Goal

Before jumping into parameter recovery, it’d be useful to know what parameter values are worth testing for each of our models. Our goal is therefore to develop an intuition about what kinds of behaviors are generated when we tweak the models’ parameters, and to find suitable parameter ranges to simulate data from.

In the case of the Successor Representation (SR), there are different variants / ways to implement this model, and so we want to make sure that arbitrary choices don’t end up having unexpected impacts.

Finally, we’ll want to simulate behaviors from many agents, so that we can get a sense for how well we can recover parameters’ ground-truth values using our parameter estimation procedure.

Setup

workflow_name <- "netnav_02_simulate_model_behaviors"

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/jaeyoungson/Documents/GitHub/network-navigation-replay
library(patchwork)

source(here("code", "utils", "modeling_utils.R"))
source(here("code", "utils", "representation_utils.R"))

source(here("code", "utils", "ggplot_themes.R"))
source(here("code", "utils", "kable_utils.R"))
## 
## Attaching package: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
source(here("code", "utils", "unicode_greek.R"))

knitting <- knitr::is_html_output()

create_path <- function(this_path) {
  if (!dir.exists(this_path)) {
    dir.create(this_path, recursive = TRUE)
  }
}

if (knitting) {
  here("figures") %>%
    create_path()
  
  here("outputs", workflow_name) %>%
    create_path()
  
  here("data", "simulated_model_behaviors") %>%
    create_path()
}
bfs_backward_sims <- here(
  "data", "bfs_sims", "bfs_sims_learned_backward.csv"
) %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    shortest_path_given_opts == shortest_path_given_start_end,
    two_correct_options == FALSE
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  select(-starts_with("shortest_path_given"), -two_correct_options) %>%
  group_by(
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id, correct_choice
  ) %>%
  summarise(
    p_bfs_correct = mean(bfs_choice == correct_choice),
    p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
    bfs_visits = mean(bfs_n_visits_total),
    .groups = "drop"
  )

bfs_forward_sims <- here("data", "bfs_sims", "bfs_sims_learned_forward.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    shortest_path_given_opts == shortest_path_given_start_end,
    two_correct_options == FALSE
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  select(-starts_with("shortest_path_given"), -two_correct_options) %>%
  group_by(
    shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id, correct_choice
  ) %>%
  summarise(
    p_bfs_correct = mean(bfs_choice == correct_choice),
    p_bfs_chooses_opt1 = mean(bfs_choice == opt1_id),
    bfs_visits = mean(bfs_n_visits_total),
    .groups = "drop"
  )

nav_trials <- here("data", "clean_data", "study1_message_passing.csv") %>%
  read_csv(show_col_types = FALSE) %>%
  filter(
    two_correct_options == FALSE,
    shortest_path_given_opts == shortest_path_given_start_end
  ) %>%
  mutate(shortest_path = factor(shortest_path_given_opts)) %>%
  filter(sub_id == 1) %>%
  select(
    shortest_path,
    startpoint_id, endpoint_id,
    opt1_id, opt2_id,
    correct_choice,
    opt1_distance = dist_opt1,
    opt2_distance = dist_opt2
  ) %>%
  arrange(shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id) %>%
  # Replace undefined distances (corresponding to impossible options)
  # so that the softmax gets non-NA inputs; we assume that impossible
  # options are just as bad as the longest distance found in this set
  # of trials, i.e., a distance of 8
  mutate(across(c(opt1_distance, opt2_distance), ~replace_na(.x, 8)))

adjlist <- here("data", "clean_data", "adjlist_learned.csv") %>%
  read_csv(show_col_types = FALSE)

transmat <- adjlist %>%
  group_by(from) %>%
  mutate(edge = edge / sum(edge)) %>%
  ungroup() %>%
  pivot_wider(names_from = to, values_from = edge) %>%
  column_to_rownames("from") %>%
  as.matrix()

BFS-backward

Model predictions

BFS-backward only has a single “search threshold” parameter that controls the agent’s tendency to “give up” and choose randomly depending on the length/difficulty of the search process.

predicted_bfs_backward <- expand_grid(
  search_threshold = 1:20,
  bfs_backward_sims
) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    p_correct = (p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
  )

When plotting out the predicted behavior from this agent, we see that the model predictions “saturate” to their asymptotic behaviors at a threshold around 15 or so.

plot_bfs_backward_trials <- predicted_bfs_backward %>%
  mutate(
    search_threshold = str_c(
      "Threshold=",
      str_pad(search_threshold, width = 2, side = "left", pad = "0")
    )
  ) %>%
  ggplot(aes(x=shortest_path, y=p_correct)) +
  theme_custom() +
  facet_wrap(~search_threshold) +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_point(alpha = 0.1) +
  stat_summary(aes(group = 1), geom = "line", fun = mean, linewidth = 1) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  ggtitle("Simulated BFS-backward")

plot_bfs_backward_summary <- predicted_bfs_backward %>%
  group_by(search_threshold, shortest_path) %>%
  summarise(p_correct = mean(p_correct), .groups = "drop") %>%
  ggplot(aes(x=shortest_path, y=p_correct, color=search_threshold)) +
  theme_custom() +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_line(aes(group = search_threshold)) +
  scale_color_viridis_c(name = "Threshold", option = "turbo", end = 0.9) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Simulated BFS-backward")

plot_bfs_backward_trials

plot_bfs_backward_summary

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_backward_trials.pdf"),
    plot = plot_bfs_backward_trials,
    width = 6, height = 6,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("outputs", workflow_name, "bfs_backward_summary.pdf"),
    plot = plot_bfs_backward_summary,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
}

Increases in the threshold parameter continue to affect distance-4 behaviors up until saturation, so let’s look at how correlated the predicted behaviors are at various thresholds. We can see that we reach essentially perfect correlation by the time we reach a threshold of 10, though visual inspection of the above plots clearly shows that there are still accuracy improvements past then. Therefore, to err on the side of being a little too liberal (which has the tendency of making parameter recovery a bit worse), we’ll stick with a threshold of 15.

plot_bfs_backward_corr <- predicted_bfs_backward %>%
  filter(shortest_path == 4) %>%
  select(search_threshold:opt2_id, p_correct) %>%
  pivot_wider(names_from = search_threshold, values_from = p_correct) %>%
  select(-c(shortest_path:opt2_id)) %>%
  cor() %>%
  as.data.frame() %>%
  rownames_to_column("from") %>%
  pivot_longer(-from, names_to = "to", values_to = "corr") %>%
  # Plotting
  mutate(
    across(c(from, to), as.integer),
    across(c(from, to), factor),
    from = fct_rev(from),
    text = round(corr, 2),
    above_midpoint = corr > 0.5
  ) %>%
  filter(from != to) %>%
  ggplot(aes(x=to, y=from, fill=corr)) +
  theme_heatmap() +
  geom_tile(show.legend = FALSE) +
  geom_text(aes(label = text, color = above_midpoint), show.legend = FALSE) +
  scale_fill_viridis_c(limits = c(0, 1)) +
  scale_color_manual(values = c("FALSE"="white", "TRUE"="black")) +
  coord_fixed() +
  ggtitle(
    "BFS-backward threshold correlation matrix",
    subtitle = "(for predicted distance-4 behavior)"
  )

plot_bfs_backward_corr

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_backward_corr.pdf"),
    plot = plot_bfs_backward_corr,
    width = 8, height = 8,
    units = "in", dpi = 300
  )
}

Simulate behavior

The final step is to actually simulate behaviors from many agents. We’ll later run this behavior through our parameter-fitting scripts to see how well we can recover them.

set.seed(sum(utf8ToInt("Ain't I the best you had?")))

sim_params_bfs_backward <- tibble(
  sub_id = 1:500,
  search_threshold = runif(n = 500, min = 1, max = 15)
)

sim_behav_bfs_backward <- expand_grid(
  sub_id = 1:500,
  bfs_backward_sims
) %>%
  # Add subject-specific parameters
  left_join(sim_params_bfs_backward, by = join_by(sub_id)) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    p_choose_opt1 = (
      (p_complete_bfs * p_bfs_chooses_opt1) + (p_give_up * 1/2)
    )
  ) %>%
  # Make binary choice in proportion to the probability of choosing opt1
  rowwise() %>%
  mutate(
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

plot_param_dist_bfs_backward <- sim_params_bfs_backward %>%
  ggplot(aes(x=search_threshold)) +
  theme_custom() +
  geom_histogram(binwidth = 1) +
  xlab("Search threshold") +
  ggtitle("BFS-backward: Simulated parameter distribution")

plot_param_dist_bfs_backward

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_backward_param_dist.pdf"),
    plot = plot_param_dist_bfs_backward,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
  
  sim_behav_bfs_backward %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_bfs_backward_no_lapse.csv"
      )
    )
}

BFS-forward

Model predictions

Like BFS-backward, BFS-forward only has a single “search threshold” parameter.

predicted_bfs_forward <- expand_grid(
  search_threshold = 1:20,
  bfs_forward_sims
) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    p_correct = (p_complete_bfs * p_bfs_correct) + (p_give_up * 1/2)
  )

We can see that by the time we get to a threshold value of 15, the predicted representation is basically saturated.

plot_bfs_forward_trials <- predicted_bfs_forward %>%
  mutate(
    search_threshold = str_c(
      "Threshold=",
      str_pad(search_threshold, width = 2, side = "left", pad = "0")
    )
  ) %>%
  ggplot(aes(x=shortest_path, y=p_correct)) +
  theme_custom() +
  facet_wrap(~search_threshold) +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_point(alpha = 0.1) +
  stat_summary(aes(group = 1), geom = "line", fun = mean, linewidth = 1) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  ggtitle("Simulated BFS-forward")

plot_bfs_forward_summary <- predicted_bfs_forward %>%
  group_by(search_threshold, shortest_path) %>%
  summarise(p_correct = mean(p_correct), .groups = "drop") %>%
  ggplot(aes(x=shortest_path, y=p_correct, color=search_threshold)) +
  theme_custom() +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_line(aes(group = search_threshold)) +
  scale_color_viridis_c(name = "Threshold", option = "turbo", end = 0.9) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Simulated BFS-forward")

plot_bfs_forward_trials

plot_bfs_forward_summary

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_forward_trials.pdf"),
    plot = plot_bfs_forward_trials,
    width = 6, height = 6,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("outputs", workflow_name, "bfs_forward_summary.pdf"),
    plot = plot_bfs_forward_summary,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
}

Reflecting this, the below correlation matrix suggests that a threshold of 16 makes (near-)perfectly correlated predictions as a threshold of 20. We’ll stick with 16 for the purpose of simulation.

plot_bfs_forward_corr <- predicted_bfs_forward %>%
  filter(shortest_path == 4) %>%
  select(search_threshold:opt2_id, p_correct) %>%
  pivot_wider(names_from = search_threshold, values_from = p_correct) %>%
  select(-c(shortest_path:opt2_id)) %>%
  cor() %>%
  as.data.frame() %>%
  rownames_to_column("from") %>%
  pivot_longer(-from, names_to = "to", values_to = "corr") %>%
  # Plotting
  mutate(
    across(c(from, to), as.integer),
    across(c(from, to), factor),
    from = fct_rev(from),
    text = round(corr, 2),
    above_midpoint = corr > 0.5
  ) %>%
  filter(from != to) %>%
  ggplot(aes(x=to, y=from, fill=corr)) +
  theme_heatmap() +
  geom_tile(show.legend = FALSE) +
  geom_text(aes(label = text, color = above_midpoint), show.legend = FALSE) +
  scale_fill_viridis_c(limits = c(0, 1)) +
  scale_color_manual(values = c("FALSE"="white", "TRUE"="black")) +
  coord_fixed() +
  ggtitle(
    "BFS-forward threshold correlation matrix",
    subtitle = "(for predicted distance-4 behavior)"
  )

plot_bfs_forward_corr

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_forward_corr.pdf"),
    plot = plot_bfs_forward_corr,
    width = 8, height = 8,
    units = "in", dpi = 300
  )
}

Simulate behavior

Note that, in a previous version of this work, we had estimated parameters for a BFS-forward model including a lapse rate parameter. It turns out that the lapse rate parameter has poor recoverability, and also seemed to bias the estimation of other parameters. To verify this, we’ll simulate two sets of behaviors: one that includes/is affected by a lapse rate, and another that does not include a lapse rate.

set.seed(sum(utf8ToInt("Slouching towards Bethlehem")))

sim_params_bfs_forward <- tibble(
  sub_id = 1:500,
  search_threshold = runif(n = 500, min = 1, max = 16),
  lapse_rate = runif(n = 500, min = 0, max = 1)
)

sim_behav_bfs_forward <- expand_grid(
  sub_id = 1:500,
  bfs_forward_sims
) %>%
  # Add subject-specific parameters
  left_join(sim_params_bfs_forward, by = join_by(sub_id)) %>%
  select(-lapse_rate) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    p_choose_opt1 = (
      (p_complete_bfs * p_bfs_chooses_opt1) + (p_give_up * 1/2)
    )
  ) %>%
  # Make binary choice in proportion to the probability of choosing opt1
  rowwise() %>%
  mutate(
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

sim_behav_bfs_forward_with_lapse <- expand_grid(
  sub_id = 1:500,
  bfs_forward_sims
) %>%
  # Add subject-specific parameters
  left_join(sim_params_bfs_forward, by = join_by(sub_id)) %>%
  # What's the probability of *completing* BFS-online all the way through?
  rowwise() %>%
  mutate(
    p_complete_bfs = softmax(
      option_values = c(search_threshold, bfs_visits),
      option_chosen = 1,
      temperature = 1
    )
  ) %>%
  ungroup() %>%
  # Weigh BFS predictions accordingly
  mutate(
    p_give_up = 1 - p_complete_bfs,
    p_choose_opt1 = (
      (p_complete_bfs * p_bfs_chooses_opt1) + (p_give_up * 1/2)
    ),
    # Add lapse rate
    # Dividing by 2 is because there are two options to choose from
    # Therefore, when lapse rate = 1, this becomes chance = 1/2
    p_choose_opt1 = p_choose_opt1 * (1-lapse_rate) + (lapse_rate/2)
  ) %>%
  # Make binary choice in proportion to the probability of choosing opt1
  rowwise() %>%
  mutate(
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

plot_param_dist_bfs_forward <- sim_params_bfs_forward %>%
  ggplot(aes(x=search_threshold)) +
  theme_custom() +
  geom_histogram(binwidth = 1) +
  xlab("Search threshold") +
  ggtitle("BFS-forward: Simulated parameter distribution")

plot_param_dist_bfs_forward

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "bfs_forward_param_dist.pdf"),
    plot = plot_param_dist_bfs_forward,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
  
  sim_behav_bfs_forward %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_bfs_forward_no_lapse.csv"
      )
    )
  
  sim_behav_bfs_forward_with_lapse %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_bfs_forward_with_lapse.csv"
      )
    )
}

Ideal observer

Model predictions

The ideal observer has, in principle, all of the information necessary to respond with perfect accuracy in the task. However, it may still continue to behave noisily, which is captured by a single softmax (inverse) temperature parameter.

predicted_ideal_obs <- expand_grid(
  softmax_temperature = -(1:20),
  nav_trials
) %>%
  rowwise() %>%
  mutate(
    p_correct = softmax(
      option_values = c(opt1_distance, opt2_distance),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

It becomes clear that the predicted behavior rapidly saturates, perhaps as soon as inverse temperature = -5.

plot_ideal_obs_trials <- predicted_ideal_obs %>%
  mutate(
    softmax_temperature = str_c(
      "Inv. temp.=-",
      str_pad(-softmax_temperature, width = 2, side = "left", pad = "0")
    )
  ) %>%
  ggplot(aes(x=shortest_path, y=p_correct)) +
  theme_custom() +
  facet_wrap(~softmax_temperature) +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_point(alpha = 0.1) +
  stat_summary(aes(group = 1), geom = "line", fun = mean, linewidth = 1) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  ggtitle("Simulated ideal observer")

plot_ideal_obs_summary <- predicted_ideal_obs %>%
  group_by(softmax_temperature, shortest_path) %>%
  summarise(p_correct = mean(p_correct), .groups = "drop") %>%
  ggplot(aes(x=shortest_path, y=p_correct, color=softmax_temperature)) +
  theme_custom() +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_line(aes(group = softmax_temperature)) +
  scale_color_viridis_c(
    name = "Inv. temp.", option = "turbo", end = 0.9, direction = -1
  ) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Simulated ideal observer")

plot_ideal_obs_trials

plot_ideal_obs_summary

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ideal_obs_trials.pdf"),
    plot = plot_ideal_obs_trials,
    width = 6, height = 6,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("outputs", workflow_name, "ideal_obs_summary.pdf"),
    plot = plot_ideal_obs_summary,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
}

This is reflected in the correlation matrix. For the purpose of simulation, we’ll limit the range to -5.

plot_ideal_obs_corr <- predicted_ideal_obs %>%
  select(softmax_temperature:opt2_id, p_correct) %>%
  pivot_wider(names_from = softmax_temperature, values_from = p_correct) %>%
  select(-c(shortest_path:opt2_id)) %>%
  cor() %>%
  as.data.frame() %>%
  rownames_to_column("from") %>%
  pivot_longer(-from, names_to = "to", values_to = "corr") %>%
  # Plotting
  mutate(
    across(c(from, to), as.integer),
    across(c(from, to), factor),
    to = fct_rev(to),
    text = round(corr, 2),
    above_midpoint = corr > 0.5
  ) %>%
  filter(from != to) %>%
  ggplot(aes(x=to, y=from, fill=corr)) +
  theme_heatmap() +
  geom_tile(show.legend = FALSE) +
  geom_text(aes(label = text, color = above_midpoint), show.legend = FALSE) +
  scale_fill_viridis_c(limits = c(0, 1)) +
  scale_color_manual(values = c("FALSE"="white", "TRUE"="black")) +
  coord_fixed() +
  ggtitle(
    "Ideal observer inverse temperature correlation matrix",
    subtitle = "(for behavior across all distances)"
  )

plot_ideal_obs_corr

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ideal_obs_corr.pdf"),
    plot = plot_ideal_obs_corr,
    width = 8, height = 8,
    units = "in", dpi = 300
  )
}

Simulate behavior

set.seed(sum(utf8ToInt("Maybe the emptiness is just a lesson in canvases")))

sim_params_ideal_obs <- tibble(
  sub_id = 1:500,
  softmax_temperature = runif(n = 500, min = -5, max = 0)
)

sim_behav_ideal_obs <- expand_grid(
  sub_id = 1:500,
  nav_trials
) %>%
  # Add subject-specific parameters
  left_join(sim_params_ideal_obs, by = join_by(sub_id)) %>%
  rowwise() %>%
  mutate(
    p_choose_opt1 = softmax(
      option_values = c(opt1_distance, opt2_distance),
      option_chosen = 1,
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    ),
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

plot_param_dist_ideal_obs <- sim_params_ideal_obs %>%
  ggplot(aes(x=softmax_temperature)) +
  theme_custom() +
  geom_histogram(binwidth = 0.5) +
  xlab("Inverse temperature") +
  ggtitle("Ideal observer: Simulated parameter distribution")

plot_param_dist_ideal_obs

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "ideal_obs_param_dist.pdf"),
    plot = plot_param_dist_ideal_obs,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
  
  sim_behav_ideal_obs %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_ideal_obs_no_lapse.csv"
      )
    )
}

Successor Representation

Overview and context

In a previous version of this work, we used a version of the Successor Representation (SR) that included a lapse rate parameter. As with BFS-forward, the lapse rate parameter ends up being hard to recover, and biases the estimation of other parameters.

In this work, we often want to simulate an “asymptotic” Successor Representation (SR). However, when using a delta-rule updating mechanism to “learn from observation”, there will inevitably be some small amount of stochasticity associated with the choice of observations. We had previously used a delta-rule mechanism in conjunction with a relatively large number of simulated learning observations, but our implementation allowed for stochasticity in the exact ordering of these observations, which (in principle) results in the predicted representation being slightly different.

In the revision, we’ll try two strategies for getting rid of unnecessary stochasticity in the parameter estimation process: 1) using a closed-form analytic solution to computing SRs, and 2) using a constant set of simulated “observations” so that the delta-rule SR consistently predicts the same representation (given the same parameters). There are, theoretically, benefits and drawbacks to both approaches, so we’ll want to try them both. The spoiler alert is that, at least for this work, both variants produce similar enough patterns of results that it functionally doesn’t matter which is used (i.e., the interpretation of results is identical).

Finally, SR-like implementations of multistep abstraction can either be conceptualized as encoding information about the number of times an agent is expected to end up in a given state, or else the probability of an agent ending up in that state. We have chosen to use an implementation that encodes probabilities, but there may be concerns that this is a researcher degree of freedom. To address this concern, we’ll also demonstrate here that both implementations contain identical information, and that it is ultimately inconsequential which implementation is used.

Analytic SR

In past research, people have generated asymptotic SRs using the following closed-form analytic solution:

\(M = (I - \gamma T)^{-1}\)

where \(M\) is the SR matrix, \(I\) is the identity matrix, \(\gamma\) is the successor horizon/discount, \(T\) is the true transition matrix, and where \(X^{-1}\) refers to the matrix inverse.

Note that the “out-of-the-box” SR produces a matrix of counts (more technically, “expected discounted future visitations”, but this is quite the mouthful). Specifically, the counts quantify “if I start at state X and take a random walk of length L, how many times should I expect to end up in state Y?” The term \(L\) can be thought of as a “lookahead” horizon, and is related to \(\gamma\) through the following equations: \(L = \frac{1}{1-\gamma}\) and \(\gamma = 1 - \frac{1}{L}\). Therefore, the SR can be normalized as a matrix of probabilities: \(M_{\text{probabilities}} \leftarrow M_{\text{counts}} \times \frac{1}{L}\), which quantify “if I start at state X and take a random walk of length L, what’s the probability of me ending up in state Y?”.

In a moment, we’ll verify that the count and probability matrices contain the same information and result in identical predicted behaviors in the social navigation task.

sr_analytic_counts <- map_dfr(
  .x = round(seq(0.1, 0.9, 0.1), 2),
  .f = ~build_successor_analytically(
    transition_matrix = transmat,
    successor_horizon = .x,
    normalize = FALSE
  )
)

sr_analytic_probs <- map_dfr(
  .x = round(seq(0.1, 0.9, 0.1), 2),
  .f = ~build_successor_analytically(
    transition_matrix = transmat,
    successor_horizon = .x,
    normalize = TRUE
  )
)

While it’s nice to have some theoretical guarantees about asymptotic representation, as well as an elegant closed-form equation, this is far from being an assumption-free solution. Specifically, the analytic solution assumes that the strongest contributor to representation is the identity matrix (i.e., state X transitioning to itself). This becomes clearer when writing out the analytic solution as a summation:

\(M = \sum_{k=0}^\infty \gamma^k T^k\)

As the \(\gamma^k\) term specifies the exponential discount/decay factor, \(\gamma^0 T^0 = I\) is quite literally the most heavily-weighted term in the summation.

We can see this pretty clearly when plotting the predicted representations:

plot_sr_analytic_counts <- sr_analytic_counts %>%
  mutate(
    across(c(from, to), factor),
    from = fct_rev(from),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=to, y=from, fill=sr_value)) +
  theme_heatmap() +
  facet_wrap(~sr_gamma) +
  geom_tile() +
  scale_fill_viridis_c(name = "SR counts") +
  coord_fixed() +
  ggtitle("Count matrix") +
  theme(
    legend.position = "bottom",
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank()
  )

plot_sr_analytic_probs <- sr_analytic_probs %>%
  mutate(
    across(c(from, to), factor),
    from = fct_rev(from),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=to, y=from, fill=sr_value)) +
  theme_heatmap() +
  facet_wrap(~sr_gamma) +
  geom_tile() +
  scale_fill_viridis_c(name = "SR probabilities") +
  coord_fixed() +
  ggtitle("Probability matrix") +
  theme(
    legend.position = "bottom",
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank()
  )

plot_sr_analytic_rep <- wrap_plots(
  plot_sr_analytic_counts, plot_sr_analytic_probs,
  nrow = 1
) &
  plot_annotation(
    title = "Analytic SR",
    theme = theme(
      plot.title = element_text(hjust = 0.5),
      legend.position = "bottom"
    )
  )

plot_sr_analytic_rep

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_analytic_rep.pdf"),
    plot = plot_sr_analytic_rep,
    width = 8, height = 6,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

At first glance, it looks like the count and probability matrices might contain different information. However, they are fundamentally the same. Let’s verify that they make exactly the same contributions to behavior when the softmax (inverse) temperature parameter accounts for the fact that the probability matrix is normalized by the lookahead. Here, the temperature for the count matrix is \(10\), and the temperature for the probability matrix is \(10L = 10 \times \frac{1}{1-\gamma}\).

predicted_sr_analytic_counts <- expand_grid(
  sr_gamma = round(seq(0.1, 0.9, 0.1), 2),
  nav_trials
) %>%
  left_join(
    sr_analytic_counts %>%
      rename(endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sr_analytic_counts %>%
      rename(endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt2_id)
  ) %>%
  rowwise() %>%
  mutate(
    p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = 10,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

predicted_sr_analytic_probs <- expand_grid(
  sr_gamma = round(seq(0.1, 0.9, 0.1), 2),
  nav_trials
) %>%
  left_join(
    sr_analytic_probs %>%
      rename(endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sr_analytic_probs %>%
      rename(endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt2_id)
  ) %>%
  rowwise() %>%
  mutate(
    lookahead = 1/(1-sr_gamma),
    p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = 10 * lookahead,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

We can see that there’s a perfect correlation between the behavioral predictions made by the count- and probability-based matrices.

left_join(
  predicted_sr_analytic_counts %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_counts = p_correct
    ),
  predicted_sr_analytic_probs %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_probs = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  group_by(sr_gamma) %>%
  nest() %>%
  mutate(
    corr = map_dbl(
      .x = data,
      .f = ~with(.x, cor(p_correct_counts, p_correct_probs))
    )
  ) %>%
  ungroup() %>%
  select(-data) %>%
  kable_custom(
    captions = c(
      "Correlations between predicted behavior from analytic SRs",
      "(counts vs probabilities)"
    )
  )
Correlations between predicted behavior from analytic SRs
(counts vs probabilities)
sr_gamma corr
0.1 1
0.2 1
0.3 1
0.4 1
0.5 1
0.6 1
0.7 1
0.8 1
0.9 1

And just to visually confirm, we can plot out the model predictions to confirm that the “monotonic relationship” being indexed by the correlation is actually “identity”.

plot_sr_analytic_nav <- left_join(
  predicted_sr_analytic_counts %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_counts = p_correct
    ),
  predicted_sr_analytic_probs %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_probs = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  pivot_longer(
    cols = starts_with("p_correct_"),
    names_to = "method",
    values_to = "p_correct"
  ) %>%
  mutate(
    method = str_remove(method, "p_correct_"),
    shortest_path = str_c("Shortest path distance ", shortest_path),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=method, y=p_correct)) +
  theme_custom() +
  facet_grid(
    rows = vars(sr_gamma),
    cols = vars(shortest_path)
  ) +
  geom_line(
    aes(group = interaction(startpoint_id, endpoint_id, opt1_id, opt2_id)),
    alpha = 0.1
  ) +
  scale_x_discrete(
    name = "Normalization method",
    labels = c("counts"="Counts", "probs"="Probabilities")
  ) +
  scale_y_continuous(
    name = "Accuracy",
    labels = scales::percent,
    breaks = seq(0.5, 1, 0.25)
  ) +
  ggtitle("Predicted navigation accuracy: Analytic SR")

plot_sr_analytic_nav

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_analytic_count_vs_prob.pdf"),
    plot = plot_sr_analytic_nav,
    width = 6, height = 8,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

Delta-rule SR

How does the “delta-rule” version of the SR compare? Let’s generate a bunch of simulated “observations”. Random walks can (and often do) get stuck within clusters/communities, so since we’re explicitly interested in asymptotic representations, let’s simulate a bunch of “paired associates” such that we cycle through all pairs (in random order) before cycling through all pairs again.

Note that we’ll generate more observations than we strictly need, just so that we have them on hand. Note also that we’ll set a random seed in the next code cell to ensure that we always generate the same observations.

set.seed(sum(utf8ToInt("Watch them, take it on back, do the rewind")))

obs_for_sr_delta_rule <- expand_grid(
  iter = 1:5000,
  adjlist %>%
    filter(edge == 1) %>%
    select(from, to)
) %>%
  group_by(iter) %>%
  slice_sample(prop = 1) %>%
  ungroup()

if (knitting) {
  here("data", "sr_obs") %>%
    create_path()
  
  obs_for_sr_delta_rule %>%
    write_csv(file = here("data", "sr_obs", "sim_obs_for_sr.csv"))
}

Let’s simulate SRs using a different number of observations each time. Note that we’re using “bidirectional” updating, meaning that observing the pair A+B triggers a learning update for both A and B.

sr_delta_100 <- map_dfr(
  .x = round(seq(0.1, 0.9, 0.1), 2),
  .f = ~build_successor_td_0(
    successor_matrix = diag(nrow = 13, ncol = 13),
    observation_matrix = obs_for_sr_delta_rule %>%
      filter(iter <= 100) %>%
      select(from, to) %>%
      as.matrix(),
    sr_alpha = 0.1,
    sr_gamma = .x,
    bidirectional = TRUE
  )
)

sr_delta_5000 <- map_dfr(
  .x = round(seq(0.1, 0.9, 0.1), 2),
  .f = ~build_successor_td_0(
    successor_matrix = diag(nrow = 13, ncol = 13),
    observation_matrix = obs_for_sr_delta_rule %>%
      filter(iter <= 5000) %>%
      select(from, to) %>%
      as.matrix(),
    sr_alpha = 0.1,
    sr_gamma = .x,
    bidirectional = TRUE
  )
)

It seems from the predicted representations that there isn’t much of a difference between using 100 observations vs 5000 observations.

plot_sr_delta_100 <- sr_delta_100 %>%
  mutate(
    across(c(from, to), factor),
    from = fct_rev(from),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=to, y=from, fill=sr_value)) +
  theme_heatmap() +
  facet_wrap(~sr_gamma) +
  geom_tile() +
  scale_fill_viridis_c(name = "SR counts", limits = c(0, 2.5)) +
  coord_fixed() +
  ggtitle("100 observations") +
  theme(
    legend.position = "bottom",
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank()
  )

plot_sr_delta_5000 <- sr_delta_5000 %>%
  mutate(
    across(c(from, to), factor),
    from = fct_rev(from),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=to, y=from, fill=sr_value)) +
  theme_heatmap() +
  facet_wrap(~sr_gamma) +
  geom_tile() +
  scale_fill_viridis_c(name = "SR counts", limits = c(0, 2.5)) +
  coord_fixed() +
  ggtitle("5000 observations") +
  theme(
    legend.position = "bottom",
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank()
  )

plot_sr_delta_rep <- wrap_plots(
  plot_sr_delta_100, plot_sr_delta_5000,
  nrow = 1, guides = "collect"
) &
  plot_annotation(
    title = "Delta-rule SR",
    theme = theme(
      plot.title = element_text(hjust = 0.5),
      legend.position = "bottom"
    )
  )

plot_sr_delta_rep

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_delta_rule_rep.pdf"),
    plot = plot_sr_delta_rep,
    width = 8, height = 6,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

We can also check whether the 100- vs 5000-observation SRs make essentially the same predictions about behavior.

predicted_sr_delta_100 <- expand_grid(
  sr_gamma = round(seq(0.1, 0.9, 0.1), 2),
  nav_trials
) %>%
  left_join(
    sr_delta_100 %>%
      select(sr_gamma, endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sr_delta_100 %>%
      select(sr_gamma, endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt2_id)
  ) %>%
  rowwise() %>%
  mutate(
    p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = 10,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

predicted_sr_delta_5000 <- expand_grid(
  sr_gamma = round(seq(0.1, 0.9, 0.1), 2),
  nav_trials
) %>%
  left_join(
    sr_delta_5000 %>%
      select(sr_gamma, endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sr_delta_5000 %>%
      select(sr_gamma, endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt2_id)
  ) %>%
  rowwise() %>%
  mutate(
    p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = 10,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

We see that there are high correlations between the predictions made by the 100- vs 5000-observation SRs:

left_join(
  predicted_sr_delta_100 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      sr_100 = p_correct
    ),
  predicted_sr_delta_5000 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      sr_5000 = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  group_by(sr_gamma) %>%
  nest() %>%
  mutate(
    corr = map_dbl(.x = data, .f = ~with(.x, cor(sr_100, sr_5000)))
  ) %>%
  ungroup() %>%
  select(-data) %>%
  kable_custom(
    captions = c(
      "Correlations between predicted behavior from delta-rule SRs",
      "(100 vs 5000 observations)"
    )
  )
Correlations between predicted behavior from delta-rule SRs
(100 vs 5000 observations)
sr_gamma corr
0.1 0.999
0.2 0.999
0.3 0.999
0.4 0.998
0.5 0.997
0.6 0.996
0.7 0.994
0.8 0.989
0.9 0.972

When we plot out the model predictions, we see that the two delta-rule SRs are making essentially the same predictions.

plot_sr_delta_nav <- left_join(
  predicted_sr_delta_100 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_100 = p_correct
    ),
  predicted_sr_delta_5000 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_5000 = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  pivot_longer(
    cols = starts_with("p_correct_"),
    names_to = "n_obs",
    values_to = "p_correct"
  ) %>%
  mutate(
    n_obs = str_remove(n_obs, "p_correct_"),
    shortest_path = str_c("Shortest path distance ", shortest_path),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=n_obs, y=p_correct)) +
  theme_custom() +
  facet_grid(
    rows = vars(sr_gamma),
    cols = vars(shortest_path)
  ) +
  geom_line(
    aes(group = interaction(startpoint_id, endpoint_id, opt1_id, opt2_id)),
    alpha = 0.1
  ) +
  scale_x_discrete(name = "# Observations") +
  scale_y_continuous(
    name = "Accuracy",
    labels = scales::percent,
    breaks = seq(0.5, 1, 0.25)
  ) +
  ggtitle("Predicted navigation accuracy: Delta-rule SR")

plot_sr_delta_nav

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_delta_rule_100_vs_5000.pdf"),
    plot = plot_sr_delta_nav,
    width = 6, height = 8,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

Analytic vs delta-rule SRs

Finally, let’s compare the predictions made by the analytic and delta-rule SRs.

We can see that the predicted behavior from the delta-rule SR (learned from 100 observations) correlates very strongly with predicted behavior from the asymptotic SR.

left_join(
  predicted_sr_delta_100 %>%
    select(sr_gamma:opt2_id, sr_100 = p_correct),
  predicted_sr_analytic_counts %>%
    select(sr_gamma:opt2_id, sr_analytic = p_correct),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  group_by(sr_gamma) %>%
  nest() %>%
  mutate(
    corr = map_dbl(.x = data, .f = ~with(.x, cor(sr_100, sr_analytic)))
  ) %>%
  ungroup() %>%
  select(-data) %>%
  kable_custom(
    captions = c(
      "Correlations between predicted behavior",
      "(delta-rule vs analytic SRs)"
    )
  )
Correlations between predicted behavior
(delta-rule vs analytic SRs)
sr_gamma corr
0.1 0.966
0.2 0.968
0.3 0.970
0.4 0.972
0.5 0.974
0.6 0.978
0.7 0.985
0.8 0.990
0.9 0.988

However, when we plot out the model predictions, we can see that there are very strong divergences in what behaviors the analytic vs delta-rule methods predict.

plot_sr_comparison_1 <- left_join(
  predicted_sr_analytic_counts %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_analytic = p_correct
    ),
  predicted_sr_delta_100 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_delta = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  pivot_longer(
    cols = starts_with("p_correct_"),
    names_to = "method",
    values_to = "p_correct"
  ) %>%
  mutate(
    method = str_remove(method, "p_correct_"),
    shortest_path = str_c("Shortest path distance ", shortest_path),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=method, y=p_correct)) +
  theme_custom() +
  facet_grid(
    rows = vars(sr_gamma),
    cols = vars(shortest_path)
  ) +
  geom_line(
    aes(group = interaction(startpoint_id, endpoint_id, opt1_id, opt2_id)),
    alpha = 0.1
  ) +
  scale_x_discrete(
    name = "Method",
    labels = c("analytic"="Analytic", "delta"="Delta-rule")
  ) +
  scale_y_continuous(
    name = "Accuracy",
    labels = scales::percent,
    breaks = seq(0.5, 1, 0.25)
  ) +
  ggtitle("Predicted navigation accuracy")

plot_sr_comparison_1

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name, "sr_analytic_vs_delta_rule_1.pdf"
    ),
    plot = plot_sr_comparison_1,
    width = 6, height = 8,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

We can emphasize this by plotting it in a different way. This highlights that (at least at this softmax temperature) the analytic and delta-rule implementations basically converge at larger values of gamma, but radically diverge at lower values of gamma.

plot_sr_comparison_2 <- left_join(
  predicted_sr_analytic_counts %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_analytic = p_correct
    ),
  predicted_sr_delta_100 %>%
    select(
      sr_gamma, shortest_path,
      startpoint_id, endpoint_id,
      opt1_id, opt2_id,
      p_correct_delta = p_correct
    ),
  by = join_by(
    sr_gamma, shortest_path, startpoint_id, endpoint_id, opt1_id, opt2_id
  )
) %>%
  pivot_longer(
    cols = starts_with("p_correct_"),
    names_to = "method",
    values_to = "p_correct"
  ) %>%
  mutate(
    method = str_remove(method, "p_correct_"),
    method = if_else(method == "analytic", "Analytic", "Delta-rule"),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=shortest_path, y=p_correct, color=method)) +
  theme_custom() +
  facet_wrap(~sr_gamma) +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  stat_summary(
    aes(group = method), geom = "line", fun = mean, linewidth = 1
  ) +
  geom_point(alpha = 0.25, show.legend = FALSE) +
  scale_color_manual(
    name = "SR implementation",
    values = c("Analytic"="#ca0020", "Delta-rule"="#0571b0")
  ) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy",
    labels = scales::percent,
    breaks = seq(0.5, 1, 0.25)
  ) +
  ggtitle("Predicted navigation accuracy") +
  theme(legend.position = "bottom")

plot_sr_comparison_2

if (knitting) {
  ggsave(
    filename = here(
      "outputs", workflow_name, "sr_analytic_vs_delta_rule_2.pdf"
    ),
    plot = plot_sr_comparison_2,
    width = 5, height = 5,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}

Model predictions

Taking stock of what we’ve demonstrated so far:

  1. It doesn’t matter whether SRs are implemented as encoding counts or probabilities. They contain the same information, and they make identical behavioral predictions when adjusting the softmax temperature parameter appropriately.

  2. The delta-rule version of the SR is sensitive to what observations it learns from. That said, we can achieve something that looks like an “asymptotic” SR with as few as 100 observations.

  3. The analytic and delta-rule SRs make highly correlated predictions. However, in terms of absolute choice accuracy, they make very different predictions at some values of gamma.

We haven’t yet established how the softmax (inverse) temperature parameter affects predicted choice, especially in combination with the gamma parameter, so we need to build an intuition for this. To do so, we’ll cheat a little bit: if you’re following these scripts in linear order, we don’t yet know that, empirically, the goodness-of-fit is essentially identical between the analytic and delta-rule variants of the SR. However, I’m writing this notebook after having done those analyses, so I’m just telling you that this is true. So, for the purpose of doing parameter recovery simulations, we’ll just use the analytic method to generate model predictions / later fit parameters on simulated data.

predicted_sr_representation <- map_dfr(
  .x = c(seq(0.1, 0.9, 0.1), 0.99),
  .f = ~build_successor_analytically(
    transmat, successor_horizon = .x, normalize = TRUE
  )
)

predicted_sr_navigation <- expand_grid(
  sr_gamma = c(seq(0.1, 0.9, 0.1), 0.99),
  softmax_temperature = seq(100, 900, 100),
  nav_trials
) %>%
  left_join(
    predicted_sr_representation %>%
      rename(endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt1_id)
  ) %>%
  left_join(
    predicted_sr_representation %>%
      rename(endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sr_gamma, endpoint_id, opt2_id)
  ) %>%
  rowwise() %>%
  mutate(
    p_correct = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = if_else(opt1_id == correct_choice, 1, 2),
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    )
  ) %>%
  ungroup()

The plots below make clear two things: 1) as a general rule, higher-gamma SRs saturate at smaller values of the inverse temperature parameter, and may in fact be hard to identify if the softmax temperature is sufficiently high, and 2) for lower-gamma SRs, saturation evidently requires fairly large values of the inverse temperature parameter. This makes it a little hard to choose good values of the temperature parameter to simulate over.

plot_sr_trials <- predicted_sr_navigation %>%
  mutate(
    softmax_temperature = str_c(
      "Inv. temp.=",
      str_pad(softmax_temperature, width = 3, side = "left", pad = "0")
    ),
    sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)
  ) %>%
  ggplot(aes(x=shortest_path, y=p_correct)) +
  theme_custom() +
  facet_grid(
    rows = vars(sr_gamma),
    cols = vars(softmax_temperature)
  ) +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_point(alpha = 0.1) +
  stat_summary(aes(group = 1), geom = "line", fun = mean, linewidth = 1) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  ggtitle("Simulated Successor Rep.")

plot_sr_summary <- predicted_sr_navigation %>%
  group_by(sr_gamma, softmax_temperature, shortest_path) %>%
  summarise(p_correct = mean(p_correct), .groups = "drop") %>%
  mutate(sr_gamma = str_c(unicode_greek["gamma"], "=", sr_gamma)) %>%
  ggplot(aes(x=shortest_path, y=p_correct, color=softmax_temperature)) +
  facet_wrap(~sr_gamma, ncol = 5) +
  theme_custom() +
  geom_hline(yintercept = 0.5, linetype = "dashed") +
  geom_line(aes(group = softmax_temperature)) +
  scale_color_viridis_c(
    name = "Inv. temp.", option = "turbo", end = 0.9, direction = -1
  ) +
  scale_x_discrete(name = "Shortest path distance") +
  scale_y_continuous(
    name = "Accuracy", labels = scales::percent, limits = c(0.5, 1)
  ) +
  theme(legend.position = "bottom") +
  ggtitle("Simulated Successor Rep.")

plot_sr_trials
## Warning: Removed 27 rows containing non-finite values (`stat_summary()`).
## Warning: Removed 27 rows containing missing values (`geom_point()`).

plot_sr_summary

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_trials.pdf"),
    plot = plot_sr_trials,
    width = 10, height = 10,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
  
  ggsave(
    filename = here("outputs", workflow_name, "sr_summary.pdf"),
    plot = plot_sr_summary,
    width = 5, height = 5,
    units = "in", dpi = 300,
    device = cairo_pdf
  )
}
## Warning: Removed 27 rows containing non-finite values (`stat_summary()`).
## Removed 27 rows containing missing values (`geom_point()`).

Simulate behavior

In this case, a good strategy might be to assume that the softmax temperatures come from a half-Gaussian distribution with a fairly liberal SD. Note that, as was the case with BFS-forward, a previous version of this work had estimated a lapse rate. To demonstrate that the lapse rate has poor recoverability, we’ll simulate two sets of behaviors, one affected by a lapse rate, one not.

set.seed(sum(utf8ToInt("Thank you, next")))

sim_params_sr <- tibble(
  sub_id = 1:500,
  sr_gamma = runif(n = 500, min = 0, max = 0.99),
  softmax_temperature = rnorm(n = 500, mean = 0, sd = 400),
  lapse_rate = runif(n = 500, min = 0, max = 1)
) %>%
  mutate(softmax_temperature = abs(softmax_temperature))

sim_rep_sr <- sim_params_sr %>%
  rowwise() %>%
  mutate(
    predicted_sr = map(
      .x = sr_gamma,
      .f = ~build_successor_analytically(
        transmat, successor_horizon = .x, normalize = TRUE
      )
    )
  ) %>%
  ungroup() %>%
  select(sub_id, predicted_sr) %>%
  unnest(predicted_sr)

sim_behav_sr <- expand_grid(
  sub_id = 1:500,
  nav_trials
) %>%
  # Add subject-specific parameters
  left_join(sim_params_sr, by = join_by(sub_id)) %>%
  select(-lapse_rate) %>%
  # Add subject-specific predicted SR
  left_join(
    sim_rep_sr %>%
      select(sub_id, endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sub_id, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sim_rep_sr %>%
      select(sub_id, endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sub_id, endpoint_id, opt2_id)
  ) %>%
  # Calculate probability of choosing opt1 given parameters + predicted SR
  # Then make binary choice in proportion to that probability
  rowwise() %>%
  mutate(
    p_choose_opt1 = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = 1,
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE
    ),
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

sim_behav_sr_with_lapse <- expand_grid(
  sub_id = 1:500,
  nav_trials
) %>%
  # Add subject-specific parameters
  left_join(sim_params_sr, by = join_by(sub_id)) %>%
  # Add subject-specific predicted SR
  left_join(
    sim_rep_sr %>%
      select(sub_id, endpoint_id = to, opt1_id = from, opt1_sr = sr_value),
    by = join_by(sub_id, endpoint_id, opt1_id)
  ) %>%
  left_join(
    sim_rep_sr %>%
      select(sub_id, endpoint_id = to, opt2_id = from, opt2_sr = sr_value),
    by = join_by(sub_id, endpoint_id, opt2_id)
  ) %>%
  # Calculate probability of choosing opt1 given parameters + predicted SR
  # Then make binary choice in proportion to that probability
  rowwise() %>%
  mutate(
    p_choose_opt1 = softmax(
      option_values = c(opt1_sr, opt2_sr),
      option_chosen = 1,
      temperature = softmax_temperature,
      use_inverse_temperature = TRUE,
      lapse_rate = lapse_rate
    ),
    simulated_choice = sample(
      c(opt1_id, opt2_id), size = 1, prob = c(p_choose_opt1, 1-p_choose_opt1)
    )
  ) %>%
  ungroup()

plot_param_dist_sr <- sim_params_sr %>%
  ggplot(aes(x=sr_gamma, y=softmax_temperature)) +
  theme_custom() +
  geom_point(alpha = 0.25) +
  xlab("Gamma") +
  ylab("Inverse temperature") +
  ggtitle("SR: Simulated parameter distribution")

plot_param_dist_sr_softmax <- sim_params_sr %>%
  ggplot(aes(x=softmax_temperature)) +
  theme_custom() +
  geom_histogram() +
  xlab("Inverse temperature") +
  ggtitle("SR: Simulated parameter distribution")

plot_param_dist_sr

plot_param_dist_sr_softmax
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

if (knitting) {
  ggsave(
    filename = here("outputs", workflow_name, "sr_param_dist.pdf"),
    plot = plot_param_dist_sr,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
  
  ggsave(
    filename = here("outputs", workflow_name, "sr_param_dist_softmax.pdf"),
    plot = plot_param_dist_sr_softmax,
    width = 5, height = 5,
    units = "in", dpi = 300
  )
  
  sim_behav_sr %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_sr_no_lapse.csv"
      )
    )
  
  sim_behav_sr_with_lapse %>%
    write_csv(
      here(
        "data", "simulated_model_behaviors",
        "sim_nav_sr_with_lapse.csv"
      )
    )
}
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Figure for supplement

The very last thing we want to do is to create a figure of the simulated a priori predictions for all planning models, to put into the supplement.

plot_nav_predictions_for_supp <- wrap_plots(
  plot_bfs_backward_summary +
    ggtitle("BFS-backward") +
    scale_color_viridis_c(
      name = "Search threshold", option = "turbo", end = 0.9
    ),
  plot_bfs_forward_summary +
    ggtitle("BFS-forward") +
    scale_color_viridis_c(
      name = "Search threshold", option = "turbo", end = 0.9
    ),
  plot_ideal_obs_summary +
    ggtitle("Ideal observer") +
    scale_color_viridis_c(
      name = "Inverse temperature",
      option = "inferno", direction = -1, begin = 0.4
    ),
  nrow = 1, guides = "collect"
) +
  plot_annotation(
    title = "Simulated behavior: Model-based planning",
    theme = theme(plot.title = element_text(hjust = 0.5)),
    tag_levels = "A", tag_suffix = "."
  ) &
  theme(legend.position = "bottom")
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
plot_nav_predictions_for_supp

if (knitting) {
  ggsave(
    filename = here("figures", "supp_sim_model_based_planning.pdf"),
    plot = plot_nav_predictions_for_supp,
    width = 8, height = 4,
    units = "in", dpi = 300
  )
}
LS0tCnRpdGxlOiAiU2ltdWxhdGUgYmVoYXZpb3IgZnJvbSBtb2RlbHMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCi0tLQoKIyBHb2FsCgpCZWZvcmUganVtcGluZyBpbnRvIHBhcmFtZXRlciByZWNvdmVyeSwgaXQnZCBiZSB1c2VmdWwgdG8ga25vdyB3aGF0IHBhcmFtZXRlciB2YWx1ZXMgYXJlIHdvcnRoIHRlc3RpbmcgZm9yIGVhY2ggb2Ygb3VyIG1vZGVscy4gT3VyIGdvYWwgaXMgdGhlcmVmb3JlIHRvIGRldmVsb3AgYW4gaW50dWl0aW9uIGFib3V0IHdoYXQga2luZHMgb2YgYmVoYXZpb3JzIGFyZSBnZW5lcmF0ZWQgd2hlbiB3ZSB0d2VhayB0aGUgbW9kZWxzJyBwYXJhbWV0ZXJzLCBhbmQgdG8gZmluZCBzdWl0YWJsZSBwYXJhbWV0ZXIgcmFuZ2VzIHRvIHNpbXVsYXRlIGRhdGEgZnJvbS4KCkluIHRoZSBjYXNlIG9mIHRoZSBTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24gKFNSKSwgdGhlcmUgYXJlIGRpZmZlcmVudCB2YXJpYW50cyAvIHdheXMgdG8gaW1wbGVtZW50IHRoaXMgbW9kZWwsIGFuZCBzbyB3ZSB3YW50IHRvIG1ha2Ugc3VyZSB0aGF0IGFyYml0cmFyeSBjaG9pY2VzIGRvbid0IGVuZCB1cCBoYXZpbmcgdW5leHBlY3RlZCBpbXBhY3RzLgoKRmluYWxseSwgd2UnbGwgd2FudCB0byBzaW11bGF0ZSBiZWhhdmlvcnMgZnJvbSBtYW55IGFnZW50cywgc28gdGhhdCB3ZSBjYW4gZ2V0IGEgc2Vuc2UgZm9yIGhvdyB3ZWxsIHdlIGNhbiByZWNvdmVyIHBhcmFtZXRlcnMnIGdyb3VuZC10cnV0aCB2YWx1ZXMgdXNpbmcgb3VyIHBhcmFtZXRlciBlc3RpbWF0aW9uIHByb2NlZHVyZS4KCgojIFNldHVwCgpgYGB7ciBsaWJyYXJpZXN9CndvcmtmbG93X25hbWUgPC0gIm5ldG5hdl8wMl9zaW11bGF0ZV9tb2RlbF9iZWhhdmlvcnMiCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShoZXJlKQpsaWJyYXJ5KHBhdGNod29yaykKCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgIm1vZGVsaW5nX3V0aWxzLlIiKSkKc291cmNlKGhlcmUoImNvZGUiLCAidXRpbHMiLCAicmVwcmVzZW50YXRpb25fdXRpbHMuUiIpKQoKc291cmNlKGhlcmUoImNvZGUiLCAidXRpbHMiLCAiZ2dwbG90X3RoZW1lcy5SIikpCnNvdXJjZShoZXJlKCJjb2RlIiwgInV0aWxzIiwgImthYmxlX3V0aWxzLlIiKSkKc291cmNlKGhlcmUoImNvZGUiLCAidXRpbHMiLCAidW5pY29kZV9ncmVlay5SIikpCgprbml0dGluZyA8LSBrbml0cjo6aXNfaHRtbF9vdXRwdXQoKQoKY3JlYXRlX3BhdGggPC0gZnVuY3Rpb24odGhpc19wYXRoKSB7CiAgaWYgKCFkaXIuZXhpc3RzKHRoaXNfcGF0aCkpIHsKICAgIGRpci5jcmVhdGUodGhpc19wYXRoLCByZWN1cnNpdmUgPSBUUlVFKQogIH0KfQoKaWYgKGtuaXR0aW5nKSB7CiAgaGVyZSgiZmlndXJlcyIpICU+JQogICAgY3JlYXRlX3BhdGgoKQogIAogIGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKICAKICBoZXJlKCJkYXRhIiwgInNpbXVsYXRlZF9tb2RlbF9iZWhhdmlvcnMiKSAlPiUKICAgIGNyZWF0ZV9wYXRoKCkKfQpgYGAKCmBgYHtyIGxvYWQtZGF0YX0KYmZzX2JhY2t3YXJkX3NpbXMgPC0gaGVyZSgKICAiZGF0YSIsICJiZnNfc2ltcyIsICJiZnNfc2ltc19sZWFybmVkX2JhY2t3YXJkLmNzdiIKKSAlPiUKICByZWFkX2NzdihzaG93X2NvbF90eXBlcyA9IEZBTFNFKSAlPiUKICBmaWx0ZXIoCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQsCiAgICB0d29fY29ycmVjdF9vcHRpb25zID09IEZBTFNFCiAgKSAlPiUKICBtdXRhdGUoc2hvcnRlc3RfcGF0aCA9IGZhY3RvcihzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMpKSAlPiUKICBzZWxlY3QoLXN0YXJ0c193aXRoKCJzaG9ydGVzdF9wYXRoX2dpdmVuIiksIC10d29fY29ycmVjdF9vcHRpb25zKSAlPiUKICBncm91cF9ieSgKICAgIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkLCBjb3JyZWN0X2Nob2ljZQogICkgJT4lCiAgc3VtbWFyaXNlKAogICAgcF9iZnNfY29ycmVjdCA9IG1lYW4oYmZzX2Nob2ljZSA9PSBjb3JyZWN0X2Nob2ljZSksCiAgICBwX2Jmc19jaG9vc2VzX29wdDEgPSBtZWFuKGJmc19jaG9pY2UgPT0gb3B0MV9pZCksCiAgICBiZnNfdmlzaXRzID0gbWVhbihiZnNfbl92aXNpdHNfdG90YWwpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCmJmc19mb3J3YXJkX3NpbXMgPC0gaGVyZSgiZGF0YSIsICJiZnNfc2ltcyIsICJiZnNfc2ltc19sZWFybmVkX2ZvcndhcmQuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgJT4lCiAgZmlsdGVyKAogICAgc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzID09IHNob3J0ZXN0X3BhdGhfZ2l2ZW5fc3RhcnRfZW5kLAogICAgdHdvX2NvcnJlY3Rfb3B0aW9ucyA9PSBGQUxTRQogICkgJT4lCiAgbXV0YXRlKHNob3J0ZXN0X3BhdGggPSBmYWN0b3Ioc2hvcnRlc3RfcGF0aF9naXZlbl9vcHRzKSkgJT4lCiAgc2VsZWN0KC1zdGFydHNfd2l0aCgic2hvcnRlc3RfcGF0aF9naXZlbiIpLCAtdHdvX2NvcnJlY3Rfb3B0aW9ucykgJT4lCiAgZ3JvdXBfYnkoCiAgICBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCwgY29ycmVjdF9jaG9pY2UKICApICU+JQogIHN1bW1hcmlzZSgKICAgIHBfYmZzX2NvcnJlY3QgPSBtZWFuKGJmc19jaG9pY2UgPT0gY29ycmVjdF9jaG9pY2UpLAogICAgcF9iZnNfY2hvb3Nlc19vcHQxID0gbWVhbihiZnNfY2hvaWNlID09IG9wdDFfaWQpLAogICAgYmZzX3Zpc2l0cyA9IG1lYW4oYmZzX25fdmlzaXRzX3RvdGFsKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApCgpuYXZfdHJpYWxzIDwtIGhlcmUoImRhdGEiLCAiY2xlYW5fZGF0YSIsICJzdHVkeTFfbWVzc2FnZV9wYXNzaW5nLmNzdiIpICU+JQogIHJlYWRfY3N2KHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQogIGZpbHRlcigKICAgIHR3b19jb3JyZWN0X29wdGlvbnMgPT0gRkFMU0UsCiAgICBzaG9ydGVzdF9wYXRoX2dpdmVuX29wdHMgPT0gc2hvcnRlc3RfcGF0aF9naXZlbl9zdGFydF9lbmQKICApICU+JQogIG11dGF0ZShzaG9ydGVzdF9wYXRoID0gZmFjdG9yKHNob3J0ZXN0X3BhdGhfZ2l2ZW5fb3B0cykpICU+JQogIGZpbHRlcihzdWJfaWQgPT0gMSkgJT4lCiAgc2VsZWN0KAogICAgc2hvcnRlc3RfcGF0aCwKICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgIGNvcnJlY3RfY2hvaWNlLAogICAgb3B0MV9kaXN0YW5jZSA9IGRpc3Rfb3B0MSwKICAgIG9wdDJfZGlzdGFuY2UgPSBkaXN0X29wdDIKICApICU+JQogIGFycmFuZ2Uoc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQpICU+JQogICMgUmVwbGFjZSB1bmRlZmluZWQgZGlzdGFuY2VzIChjb3JyZXNwb25kaW5nIHRvIGltcG9zc2libGUgb3B0aW9ucykKICAjIHNvIHRoYXQgdGhlIHNvZnRtYXggZ2V0cyBub24tTkEgaW5wdXRzOyB3ZSBhc3N1bWUgdGhhdCBpbXBvc3NpYmxlCiAgIyBvcHRpb25zIGFyZSBqdXN0IGFzIGJhZCBhcyB0aGUgbG9uZ2VzdCBkaXN0YW5jZSBmb3VuZCBpbiB0aGlzIHNldAogICMgb2YgdHJpYWxzLCBpLmUuLCBhIGRpc3RhbmNlIG9mIDgKICBtdXRhdGUoYWNyb3NzKGMob3B0MV9kaXN0YW5jZSwgb3B0Ml9kaXN0YW5jZSksIH5yZXBsYWNlX25hKC54LCA4KSkpCgphZGpsaXN0IDwtIGhlcmUoImRhdGEiLCAiY2xlYW5fZGF0YSIsICJhZGpsaXN0X2xlYXJuZWQuY3N2IikgJT4lCiAgcmVhZF9jc3Yoc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKCnRyYW5zbWF0IDwtIGFkamxpc3QgJT4lCiAgZ3JvdXBfYnkoZnJvbSkgJT4lCiAgbXV0YXRlKGVkZ2UgPSBlZGdlIC8gc3VtKGVkZ2UpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHRvLCB2YWx1ZXNfZnJvbSA9IGVkZ2UpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiZnJvbSIpICU+JQogIGFzLm1hdHJpeCgpCmBgYAoKCiMgQkZTLWJhY2t3YXJkCgojIyBNb2RlbCBwcmVkaWN0aW9ucwoKQkZTLWJhY2t3YXJkIG9ubHkgaGFzIGEgc2luZ2xlICJzZWFyY2ggdGhyZXNob2xkIiBwYXJhbWV0ZXIgdGhhdCBjb250cm9scyB0aGUgYWdlbnQncyB0ZW5kZW5jeSB0byAiZ2l2ZSB1cCIgYW5kIGNob29zZSByYW5kb21seSBkZXBlbmRpbmcgb24gdGhlIGxlbmd0aC9kaWZmaWN1bHR5IG9mIHRoZSBzZWFyY2ggcHJvY2Vzcy4KCmBgYHtyIGJmcy1iYWNrd2FyZC1wcmVkaWN0ZWR9CnByZWRpY3RlZF9iZnNfYmFja3dhcmQgPC0gZXhwYW5kX2dyaWQoCiAgc2VhcmNoX3RocmVzaG9sZCA9IDE6MjAsCiAgYmZzX2JhY2t3YXJkX3NpbXMKKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgcF9jb3JyZWN0ID0gKHBfY29tcGxldGVfYmZzICogcF9iZnNfY29ycmVjdCkgKyAocF9naXZlX3VwICogMS8yKQogICkKYGBgCgpXaGVuIHBsb3R0aW5nIG91dCB0aGUgcHJlZGljdGVkIGJlaGF2aW9yIGZyb20gdGhpcyBhZ2VudCwgd2Ugc2VlIHRoYXQgdGhlIG1vZGVsIHByZWRpY3Rpb25zICJzYXR1cmF0ZSIgdG8gdGhlaXIgYXN5bXB0b3RpYyBiZWhhdmlvcnMgYXQgYSB0aHJlc2hvbGQgYXJvdW5kIDE1IG9yIHNvLgoKYGBge3IgcGxvdC1iZnMtYmFja3dhcmR9CiN8IGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTYKCnBsb3RfYmZzX2JhY2t3YXJkX3RyaWFscyA8LSBwcmVkaWN0ZWRfYmZzX2JhY2t3YXJkICU+JQogIG11dGF0ZSgKICAgIHNlYXJjaF90aHJlc2hvbGQgPSBzdHJfYygKICAgICAgIlRocmVzaG9sZD0iLAogICAgICBzdHJfcGFkKHNlYXJjaF90aHJlc2hvbGQsIHdpZHRoID0gMiwgc2lkZSA9ICJsZWZ0IiwgcGFkID0gIjAiKQogICAgKQogICkgJT4lCiAgZ2dwbG90KGFlcyh4PXNob3J0ZXN0X3BhdGgsIHk9cF9jb3JyZWN0KSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zZWFyY2hfdGhyZXNob2xkKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKwogIHN0YXRfc3VtbWFyeShhZXMoZ3JvdXAgPSAxKSwgZ2VvbSA9ICJsaW5lIiwgZnVuID0gbWVhbiwgbGluZXdpZHRoID0gMSkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQWNjdXJhY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMC41LCAxKQogICkgKwogIGdndGl0bGUoIlNpbXVsYXRlZCBCRlMtYmFja3dhcmQiKQoKcGxvdF9iZnNfYmFja3dhcmRfc3VtbWFyeSA8LSBwcmVkaWN0ZWRfYmZzX2JhY2t3YXJkICU+JQogIGdyb3VwX2J5KHNlYXJjaF90aHJlc2hvbGQsIHNob3J0ZXN0X3BhdGgpICU+JQogIHN1bW1hcmlzZShwX2NvcnJlY3QgPSBtZWFuKHBfY29ycmVjdCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfY29ycmVjdCwgY29sb3I9c2VhcmNoX3RocmVzaG9sZCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHNlYXJjaF90aHJlc2hvbGQpKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG5hbWUgPSAiVGhyZXNob2xkIiwgb3B0aW9uID0gInR1cmJvIiwgZW5kID0gMC45KSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLjUsIDEpCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBnZ3RpdGxlKCJTaW11bGF0ZWQgQkZTLWJhY2t3YXJkIikKCnBsb3RfYmZzX2JhY2t3YXJkX3RyaWFscwpwbG90X2Jmc19iYWNrd2FyZF9zdW1tYXJ5CgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiYmZzX2JhY2t3YXJkX3RyaWFscy5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2Jmc19iYWNrd2FyZF90cmlhbHMsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiYmZzX2JhY2t3YXJkX3N1bW1hcnkucGRmIiksCiAgICBwbG90ID0gcGxvdF9iZnNfYmFja3dhcmRfc3VtbWFyeSwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKSW5jcmVhc2VzIGluIHRoZSB0aHJlc2hvbGQgcGFyYW1ldGVyIGNvbnRpbnVlIHRvIGFmZmVjdCBkaXN0YW5jZS00IGJlaGF2aW9ycyB1cCB1bnRpbCBzYXR1cmF0aW9uLCBzbyBsZXQncyBsb29rIGF0IGhvdyBjb3JyZWxhdGVkIHRoZSBwcmVkaWN0ZWQgYmVoYXZpb3JzIGFyZSBhdCB2YXJpb3VzIHRocmVzaG9sZHMuIFdlIGNhbiBzZWUgdGhhdCB3ZSByZWFjaCBlc3NlbnRpYWxseSBwZXJmZWN0IGNvcnJlbGF0aW9uIGJ5IHRoZSB0aW1lIHdlIHJlYWNoIGEgdGhyZXNob2xkIG9mIDEwLCB0aG91Z2ggdmlzdWFsIGluc3BlY3Rpb24gb2YgdGhlIGFib3ZlIHBsb3RzIGNsZWFybHkgc2hvd3MgdGhhdCB0aGVyZSBhcmUgc3RpbGwgYWNjdXJhY3kgaW1wcm92ZW1lbnRzIHBhc3QgdGhlbi4gVGhlcmVmb3JlLCB0byBlcnIgb24gdGhlIHNpZGUgb2YgYmVpbmcgYSBsaXR0bGUgdG9vIGxpYmVyYWwgKHdoaWNoIGhhcyB0aGUgdGVuZGVuY3kgb2YgbWFraW5nIHBhcmFtZXRlciByZWNvdmVyeSBhIGJpdCB3b3JzZSksIHdlJ2xsIHN0aWNrIHdpdGggYSB0aHJlc2hvbGQgb2YgMTUuCgpgYGB7ciBjb3JyLWJmcy1iYWNrd2FyZH0KI3wgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OAoKcGxvdF9iZnNfYmFja3dhcmRfY29yciA8LSBwcmVkaWN0ZWRfYmZzX2JhY2t3YXJkICU+JQogIGZpbHRlcihzaG9ydGVzdF9wYXRoID09IDQpICU+JQogIHNlbGVjdChzZWFyY2hfdGhyZXNob2xkOm9wdDJfaWQsIHBfY29ycmVjdCkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNlYXJjaF90aHJlc2hvbGQsIHZhbHVlc19mcm9tID0gcF9jb3JyZWN0KSAlPiUKICBzZWxlY3QoLWMoc2hvcnRlc3RfcGF0aDpvcHQyX2lkKSkgJT4lCiAgY29yKCkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiZnJvbSIpICU+JQogIHBpdm90X2xvbmdlcigtZnJvbSwgbmFtZXNfdG8gPSAidG8iLCB2YWx1ZXNfdG8gPSAiY29yciIpICU+JQogICMgUGxvdHRpbmcKICBtdXRhdGUoCiAgICBhY3Jvc3MoYyhmcm9tLCB0byksIGFzLmludGVnZXIpLAogICAgYWNyb3NzKGMoZnJvbSwgdG8pLCBmYWN0b3IpLAogICAgZnJvbSA9IGZjdF9yZXYoZnJvbSksCiAgICB0ZXh0ID0gcm91bmQoY29yciwgMiksCiAgICBhYm92ZV9taWRwb2ludCA9IGNvcnIgPiAwLjUKICApICU+JQogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dG8sIHk9ZnJvbSwgZmlsbD1jb3JyKSkgKwogIHRoZW1lX2hlYXRtYXAoKSArCiAgZ2VvbV90aWxlKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gdGV4dCwgY29sb3IgPSBhYm92ZV9taWRwb2ludCksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhsaW1pdHMgPSBjKDAsIDEpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIkZBTFNFIj0id2hpdGUiLCAiVFJVRSI9ImJsYWNrIikpICsKICBjb29yZF9maXhlZCgpICsKICBnZ3RpdGxlKAogICAgIkJGUy1iYWNrd2FyZCB0aHJlc2hvbGQgY29ycmVsYXRpb24gbWF0cml4IiwKICAgIHN1YnRpdGxlID0gIihmb3IgcHJlZGljdGVkIGRpc3RhbmNlLTQgYmVoYXZpb3IpIgogICkKCnBsb3RfYmZzX2JhY2t3YXJkX2NvcnIKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJiZnNfYmFja3dhcmRfY29yci5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2Jmc19iYWNrd2FyZF9jb3JyLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA4LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgoKIyMgU2ltdWxhdGUgYmVoYXZpb3IKClRoZSBmaW5hbCBzdGVwIGlzIHRvIGFjdHVhbGx5IHNpbXVsYXRlIGJlaGF2aW9ycyBmcm9tIG1hbnkgYWdlbnRzLiBXZSdsbCBsYXRlciBydW4gdGhpcyBiZWhhdmlvciB0aHJvdWdoIG91ciBwYXJhbWV0ZXItZml0dGluZyBzY3JpcHRzIHRvIHNlZSBob3cgd2VsbCB3ZSBjYW4gcmVjb3ZlciB0aGVtLgoKYGBge3Igc2ltLWJmcy1iYWNrd2FyZH0Kc2V0LnNlZWQoc3VtKHV0ZjhUb0ludCgiQWluJ3QgSSB0aGUgYmVzdCB5b3UgaGFkPyIpKSkKCnNpbV9wYXJhbXNfYmZzX2JhY2t3YXJkIDwtIHRpYmJsZSgKICBzdWJfaWQgPSAxOjUwMCwKICBzZWFyY2hfdGhyZXNob2xkID0gcnVuaWYobiA9IDUwMCwgbWluID0gMSwgbWF4ID0gMTUpCikKCnNpbV9iZWhhdl9iZnNfYmFja3dhcmQgPC0gZXhwYW5kX2dyaWQoCiAgc3ViX2lkID0gMTo1MDAsCiAgYmZzX2JhY2t3YXJkX3NpbXMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oc2ltX3BhcmFtc19iZnNfYmFja3dhcmQsIGJ5ID0gam9pbl9ieShzdWJfaWQpKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgcF9jaG9vc2Vfb3B0MSA9ICgKICAgICAgKHBfY29tcGxldGVfYmZzICogcF9iZnNfY2hvb3Nlc19vcHQxKSArIChwX2dpdmVfdXAgKiAxLzIpCiAgICApCiAgKSAlPiUKICAjIE1ha2UgYmluYXJ5IGNob2ljZSBpbiBwcm9wb3J0aW9uIHRvIHRoZSBwcm9iYWJpbGl0eSBvZiBjaG9vc2luZyBvcHQxCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHNpbXVsYXRlZF9jaG9pY2UgPSBzYW1wbGUoCiAgICAgIGMob3B0MV9pZCwgb3B0Ml9pZCksIHNpemUgPSAxLCBwcm9iID0gYyhwX2Nob29zZV9vcHQxLCAxLXBfY2hvb3NlX29wdDEpCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKCnBsb3RfcGFyYW1fZGlzdF9iZnNfYmFja3dhcmQgPC0gc2ltX3BhcmFtc19iZnNfYmFja3dhcmQgJT4lCiAgZ2dwbG90KGFlcyh4PXNlYXJjaF90aHJlc2hvbGQpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkgKwogIHhsYWIoIlNlYXJjaCB0aHJlc2hvbGQiKSArCiAgZ2d0aXRsZSgiQkZTLWJhY2t3YXJkOiBTaW11bGF0ZWQgcGFyYW1ldGVyIGRpc3RyaWJ1dGlvbiIpCgpwbG90X3BhcmFtX2Rpc3RfYmZzX2JhY2t3YXJkCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiYmZzX2JhY2t3YXJkX3BhcmFtX2Rpc3QucGRmIiksCiAgICBwbG90ID0gcGxvdF9wYXJhbV9kaXN0X2Jmc19iYWNrd2FyZCwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQogIAogIHNpbV9iZWhhdl9iZnNfYmFja3dhcmQgJT4lCiAgICB3cml0ZV9jc3YoCiAgICAgIGhlcmUoCiAgICAgICAgImRhdGEiLCAic2ltdWxhdGVkX21vZGVsX2JlaGF2aW9ycyIsCiAgICAgICAgInNpbV9uYXZfYmZzX2JhY2t3YXJkX25vX2xhcHNlLmNzdiIKICAgICAgKQogICAgKQp9CmBgYAoKCiMgQkZTLWZvcndhcmQKCiMjIE1vZGVsIHByZWRpY3Rpb25zCgpMaWtlIEJGUy1iYWNrd2FyZCwgQkZTLWZvcndhcmQgb25seSBoYXMgYSBzaW5nbGUgInNlYXJjaCB0aHJlc2hvbGQiIHBhcmFtZXRlci4KCmBgYHtyIGJmcy1mb3J3YXJkLXByZWRpY3RlZH0KcHJlZGljdGVkX2Jmc19mb3J3YXJkIDwtIGV4cGFuZF9ncmlkKAogIHNlYXJjaF90aHJlc2hvbGQgPSAxOjIwLAogIGJmc19mb3J3YXJkX3NpbXMKKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgcF9jb3JyZWN0ID0gKHBfY29tcGxldGVfYmZzICogcF9iZnNfY29ycmVjdCkgKyAocF9naXZlX3VwICogMS8yKQogICkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgYnkgdGhlIHRpbWUgd2UgZ2V0IHRvIGEgdGhyZXNob2xkIHZhbHVlIG9mIDE1LCB0aGUgcHJlZGljdGVkIHJlcHJlc2VudGF0aW9uIGlzIGJhc2ljYWxseSBzYXR1cmF0ZWQuCgpgYGB7ciBwbG90LWJmcy1mb3J3YXJkfQojfCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02CgpwbG90X2Jmc19mb3J3YXJkX3RyaWFscyA8LSBwcmVkaWN0ZWRfYmZzX2ZvcndhcmQgJT4lCiAgbXV0YXRlKAogICAgc2VhcmNoX3RocmVzaG9sZCA9IHN0cl9jKAogICAgICAiVGhyZXNob2xkPSIsCiAgICAgIHN0cl9wYWQoc2VhcmNoX3RocmVzaG9sZCwgd2lkdGggPSAyLCBzaWRlID0gImxlZnQiLCBwYWQgPSAiMCIpCiAgICApCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9c2hvcnRlc3RfcGF0aCwgeT1wX2NvcnJlY3QpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGZhY2V0X3dyYXAofnNlYXJjaF90aHJlc2hvbGQpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArCiAgc3RhdF9zdW1tYXJ5KGFlcyhncm91cCA9IDEpLCBnZW9tID0gImxpbmUiLCBmdW4gPSBtZWFuLCBsaW5ld2lkdGggPSAxKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLjUsIDEpCiAgKSArCiAgZ2d0aXRsZSgiU2ltdWxhdGVkIEJGUy1mb3J3YXJkIikKCnBsb3RfYmZzX2ZvcndhcmRfc3VtbWFyeSA8LSBwcmVkaWN0ZWRfYmZzX2ZvcndhcmQgJT4lCiAgZ3JvdXBfYnkoc2VhcmNoX3RocmVzaG9sZCwgc2hvcnRlc3RfcGF0aCkgJT4lCiAgc3VtbWFyaXNlKHBfY29ycmVjdCA9IG1lYW4ocF9jb3JyZWN0KSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lCiAgZ2dwbG90KGFlcyh4PXNob3J0ZXN0X3BhdGgsIHk9cF9jb3JyZWN0LCBjb2xvcj1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gc2VhcmNoX3RocmVzaG9sZCkpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MobmFtZSA9ICJUaHJlc2hvbGQiLCBvcHRpb24gPSAidHVyYm8iLCBlbmQgPSAwLjkpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkFjY3VyYWN5IiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAuNSwgMSkKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGdndGl0bGUoIlNpbXVsYXRlZCBCRlMtZm9yd2FyZCIpCgpwbG90X2Jmc19mb3J3YXJkX3RyaWFscwpwbG90X2Jmc19mb3J3YXJkX3N1bW1hcnkKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJiZnNfZm9yd2FyZF90cmlhbHMucGRmIiksCiAgICBwbG90ID0gcGxvdF9iZnNfZm9yd2FyZF90cmlhbHMsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiYmZzX2ZvcndhcmRfc3VtbWFyeS5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2Jmc19mb3J3YXJkX3N1bW1hcnksCiAgICB3aWR0aCA9IDUsIGhlaWdodCA9IDUsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKfQpgYGAKClJlZmxlY3RpbmcgdGhpcywgdGhlIGJlbG93IGNvcnJlbGF0aW9uIG1hdHJpeCBzdWdnZXN0cyB0aGF0IGEgdGhyZXNob2xkIG9mIDE2IG1ha2VzIChuZWFyLSlwZXJmZWN0bHkgY29ycmVsYXRlZCBwcmVkaWN0aW9ucyBhcyBhIHRocmVzaG9sZCBvZiAyMC4gV2UnbGwgc3RpY2sgd2l0aCAxNiBmb3IgdGhlIHB1cnBvc2Ugb2Ygc2ltdWxhdGlvbi4KCmBgYHtyIGNvcnItYmZzLWZvcndhcmR9CiN8IGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTgKCnBsb3RfYmZzX2ZvcndhcmRfY29yciA8LSBwcmVkaWN0ZWRfYmZzX2ZvcndhcmQgJT4lCiAgZmlsdGVyKHNob3J0ZXN0X3BhdGggPT0gNCkgJT4lCiAgc2VsZWN0KHNlYXJjaF90aHJlc2hvbGQ6b3B0Ml9pZCwgcF9jb3JyZWN0KSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc2VhcmNoX3RocmVzaG9sZCwgdmFsdWVzX2Zyb20gPSBwX2NvcnJlY3QpICU+JQogIHNlbGVjdCgtYyhzaG9ydGVzdF9wYXRoOm9wdDJfaWQpKSAlPiUKICBjb3IoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJmcm9tIikgJT4lCiAgcGl2b3RfbG9uZ2VyKC1mcm9tLCBuYW1lc190byA9ICJ0byIsIHZhbHVlc190byA9ICJjb3JyIikgJT4lCiAgIyBQbG90dGluZwogIG11dGF0ZSgKICAgIGFjcm9zcyhjKGZyb20sIHRvKSwgYXMuaW50ZWdlciksCiAgICBhY3Jvc3MoYyhmcm9tLCB0byksIGZhY3RvciksCiAgICBmcm9tID0gZmN0X3Jldihmcm9tKSwKICAgIHRleHQgPSByb3VuZChjb3JyLCAyKSwKICAgIGFib3ZlX21pZHBvaW50ID0gY29yciA+IDAuNQogICkgJT4lCiAgZmlsdGVyKGZyb20gIT0gdG8pICU+JQogIGdncGxvdChhZXMoeD10bywgeT1mcm9tLCBmaWxsPWNvcnIpKSArCiAgdGhlbWVfaGVhdG1hcCgpICsKICBnZW9tX3RpbGUoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSB0ZXh0LCBjb2xvciA9IGFib3ZlX21pZHBvaW50KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKGxpbWl0cyA9IGMoMCwgMSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiRkFMU0UiPSJ3aGl0ZSIsICJUUlVFIj0iYmxhY2siKSkgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGdndGl0bGUoCiAgICAiQkZTLWZvcndhcmQgdGhyZXNob2xkIGNvcnJlbGF0aW9uIG1hdHJpeCIsCiAgICBzdWJ0aXRsZSA9ICIoZm9yIHByZWRpY3RlZCBkaXN0YW5jZS00IGJlaGF2aW9yKSIKICApCgpwbG90X2Jmc19mb3J3YXJkX2NvcnIKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJiZnNfZm9yd2FyZF9jb3JyLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfYmZzX2ZvcndhcmRfY29yciwKICAgIHdpZHRoID0gOCwgaGVpZ2h0ID0gOCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKIyMgU2ltdWxhdGUgYmVoYXZpb3IKCk5vdGUgdGhhdCwgaW4gYSBwcmV2aW91cyB2ZXJzaW9uIG9mIHRoaXMgd29yaywgd2UgaGFkIGVzdGltYXRlZCBwYXJhbWV0ZXJzIGZvciBhIEJGUy1mb3J3YXJkIG1vZGVsIGluY2x1ZGluZyBhIGxhcHNlIHJhdGUgcGFyYW1ldGVyLiBJdCB0dXJucyBvdXQgdGhhdCB0aGUgbGFwc2UgcmF0ZSBwYXJhbWV0ZXIgaGFzIHBvb3IgcmVjb3ZlcmFiaWxpdHksIGFuZCBhbHNvIHNlZW1lZCB0byBiaWFzIHRoZSBlc3RpbWF0aW9uIG9mIG90aGVyIHBhcmFtZXRlcnMuIFRvIHZlcmlmeSB0aGlzLCB3ZSdsbCBzaW11bGF0ZSB0d28gc2V0cyBvZiBiZWhhdmlvcnM6IG9uZSB0aGF0IGluY2x1ZGVzL2lzIGFmZmVjdGVkIGJ5IGEgbGFwc2UgcmF0ZSwgYW5kIGFub3RoZXIgdGhhdCBkb2VzIG5vdCBpbmNsdWRlIGEgbGFwc2UgcmF0ZS4KCmBgYHtyIHNpbS1iZnMtZm9yd2FyZH0Kc2V0LnNlZWQoc3VtKHV0ZjhUb0ludCgiU2xvdWNoaW5nIHRvd2FyZHMgQmV0aGxlaGVtIikpKQoKc2ltX3BhcmFtc19iZnNfZm9yd2FyZCA8LSB0aWJibGUoCiAgc3ViX2lkID0gMTo1MDAsCiAgc2VhcmNoX3RocmVzaG9sZCA9IHJ1bmlmKG4gPSA1MDAsIG1pbiA9IDEsIG1heCA9IDE2KSwKICBsYXBzZV9yYXRlID0gcnVuaWYobiA9IDUwMCwgbWluID0gMCwgbWF4ID0gMSkKKQoKc2ltX2JlaGF2X2Jmc19mb3J3YXJkIDwtIGV4cGFuZF9ncmlkKAogIHN1Yl9pZCA9IDE6NTAwLAogIGJmc19mb3J3YXJkX3NpbXMKKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHBhcmFtZXRlcnMKICBsZWZ0X2pvaW4oc2ltX3BhcmFtc19iZnNfZm9yd2FyZCwgYnkgPSBqb2luX2J5KHN1Yl9pZCkpICU+JQogIHNlbGVjdCgtbGFwc2VfcmF0ZSkgJT4lCiAgIyBXaGF0J3MgdGhlIHByb2JhYmlsaXR5IG9mICpjb21wbGV0aW5nKiBCRlMtb25saW5lIGFsbCB0aGUgd2F5IHRocm91Z2g/CiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHBfY29tcGxldGVfYmZzID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMoc2VhcmNoX3RocmVzaG9sZCwgYmZzX3Zpc2l0cyksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSAxLAogICAgICB0ZW1wZXJhdHVyZSA9IDEKICAgICkKICApICU+JQogIHVuZ3JvdXAoKSAlPiUKICAjIFdlaWdoIEJGUyBwcmVkaWN0aW9ucyBhY2NvcmRpbmdseQogIG11dGF0ZSgKICAgIHBfZ2l2ZV91cCA9IDEgLSBwX2NvbXBsZXRlX2JmcywKICAgIHBfY2hvb3NlX29wdDEgPSAoCiAgICAgIChwX2NvbXBsZXRlX2JmcyAqIHBfYmZzX2Nob29zZXNfb3B0MSkgKyAocF9naXZlX3VwICogMS8yKQogICAgKQogICkgJT4lCiAgIyBNYWtlIGJpbmFyeSBjaG9pY2UgaW4gcHJvcG9ydGlvbiB0byB0aGUgcHJvYmFiaWxpdHkgb2YgY2hvb3Npbmcgb3B0MQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBzaW11bGF0ZWRfY2hvaWNlID0gc2FtcGxlKAogICAgICBjKG9wdDFfaWQsIG9wdDJfaWQpLCBzaXplID0gMSwgcHJvYiA9IGMocF9jaG9vc2Vfb3B0MSwgMS1wX2Nob29zZV9vcHQxKQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpCgpzaW1fYmVoYXZfYmZzX2ZvcndhcmRfd2l0aF9sYXBzZSA8LSBleHBhbmRfZ3JpZCgKICBzdWJfaWQgPSAxOjUwMCwKICBiZnNfZm9yd2FyZF9zaW1zCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKHNpbV9wYXJhbXNfYmZzX2ZvcndhcmQsIGJ5ID0gam9pbl9ieShzdWJfaWQpKSAlPiUKICAjIFdoYXQncyB0aGUgcHJvYmFiaWxpdHkgb2YgKmNvbXBsZXRpbmcqIEJGUy1vbmxpbmUgYWxsIHRoZSB3YXkgdGhyb3VnaD8KICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb21wbGV0ZV9iZnMgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhzZWFyY2hfdGhyZXNob2xkLCBiZnNfdmlzaXRzKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IDEsCiAgICAgIHRlbXBlcmF0dXJlID0gMQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogICMgV2VpZ2ggQkZTIHByZWRpY3Rpb25zIGFjY29yZGluZ2x5CiAgbXV0YXRlKAogICAgcF9naXZlX3VwID0gMSAtIHBfY29tcGxldGVfYmZzLAogICAgcF9jaG9vc2Vfb3B0MSA9ICgKICAgICAgKHBfY29tcGxldGVfYmZzICogcF9iZnNfY2hvb3Nlc19vcHQxKSArIChwX2dpdmVfdXAgKiAxLzIpCiAgICApLAogICAgIyBBZGQgbGFwc2UgcmF0ZQogICAgIyBEaXZpZGluZyBieSAyIGlzIGJlY2F1c2UgdGhlcmUgYXJlIHR3byBvcHRpb25zIHRvIGNob29zZSBmcm9tCiAgICAjIFRoZXJlZm9yZSwgd2hlbiBsYXBzZSByYXRlID0gMSwgdGhpcyBiZWNvbWVzIGNoYW5jZSA9IDEvMgogICAgcF9jaG9vc2Vfb3B0MSA9IHBfY2hvb3NlX29wdDEgKiAoMS1sYXBzZV9yYXRlKSArIChsYXBzZV9yYXRlLzIpCiAgKSAlPiUKICAjIE1ha2UgYmluYXJ5IGNob2ljZSBpbiBwcm9wb3J0aW9uIHRvIHRoZSBwcm9iYWJpbGl0eSBvZiBjaG9vc2luZyBvcHQxCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHNpbXVsYXRlZF9jaG9pY2UgPSBzYW1wbGUoCiAgICAgIGMob3B0MV9pZCwgb3B0Ml9pZCksIHNpemUgPSAxLCBwcm9iID0gYyhwX2Nob29zZV9vcHQxLCAxLXBfY2hvb3NlX29wdDEpCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKCnBsb3RfcGFyYW1fZGlzdF9iZnNfZm9yd2FyZCA8LSBzaW1fcGFyYW1zX2Jmc19mb3J3YXJkICU+JQogIGdncGxvdChhZXMoeD1zZWFyY2hfdGhyZXNob2xkKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsKICB4bGFiKCJTZWFyY2ggdGhyZXNob2xkIikgKwogIGdndGl0bGUoIkJGUy1mb3J3YXJkOiBTaW11bGF0ZWQgcGFyYW1ldGVyIGRpc3RyaWJ1dGlvbiIpCgpwbG90X3BhcmFtX2Rpc3RfYmZzX2ZvcndhcmQKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJiZnNfZm9yd2FyZF9wYXJhbV9kaXN0LnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcGFyYW1fZGlzdF9iZnNfZm9yd2FyZCwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQogIAogIHNpbV9iZWhhdl9iZnNfZm9yd2FyZCAlPiUKICAgIHdyaXRlX2NzdigKICAgICAgaGVyZSgKICAgICAgICAiZGF0YSIsICJzaW11bGF0ZWRfbW9kZWxfYmVoYXZpb3JzIiwKICAgICAgICAic2ltX25hdl9iZnNfZm9yd2FyZF9ub19sYXBzZS5jc3YiCiAgICAgICkKICAgICkKICAKICBzaW1fYmVoYXZfYmZzX2ZvcndhcmRfd2l0aF9sYXBzZSAlPiUKICAgIHdyaXRlX2NzdigKICAgICAgaGVyZSgKICAgICAgICAiZGF0YSIsICJzaW11bGF0ZWRfbW9kZWxfYmVoYXZpb3JzIiwKICAgICAgICAic2ltX25hdl9iZnNfZm9yd2FyZF93aXRoX2xhcHNlLmNzdiIKICAgICAgKQogICAgKQp9CmBgYAoKCiMgSWRlYWwgb2JzZXJ2ZXIKCiMjIE1vZGVsIHByZWRpY3Rpb25zCgpUaGUgaWRlYWwgb2JzZXJ2ZXIgaGFzLCBpbiBwcmluY2lwbGUsIGFsbCBvZiB0aGUgaW5mb3JtYXRpb24gbmVjZXNzYXJ5IHRvIHJlc3BvbmQgd2l0aCBwZXJmZWN0IGFjY3VyYWN5IGluIHRoZSB0YXNrLiBIb3dldmVyLCBpdCBtYXkgc3RpbGwgY29udGludWUgdG8gYmVoYXZlIG5vaXNpbHksIHdoaWNoIGlzIGNhcHR1cmVkIGJ5IGEgc2luZ2xlIHNvZnRtYXggKGludmVyc2UpIHRlbXBlcmF0dXJlIHBhcmFtZXRlci4KCmBgYHtyIGlkZWFsLW9icy1wcmVkaWN0ZWR9CnByZWRpY3RlZF9pZGVhbF9vYnMgPC0gZXhwYW5kX2dyaWQoCiAgc29mdG1heF90ZW1wZXJhdHVyZSA9IC0oMToyMCksCiAgbmF2X3RyaWFscwopICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwX2NvcnJlY3QgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhvcHQxX2Rpc3RhbmNlLCBvcHQyX2Rpc3RhbmNlKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IGlmX2Vsc2Uob3B0MV9pZCA9PSBjb3JyZWN0X2Nob2ljZSwgMSwgMiksCiAgICAgIHRlbXBlcmF0dXJlID0gc29mdG1heF90ZW1wZXJhdHVyZSwKICAgICAgdXNlX2ludmVyc2VfdGVtcGVyYXR1cmUgPSBUUlVFCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKYGBgCgpJdCBiZWNvbWVzIGNsZWFyIHRoYXQgdGhlIHByZWRpY3RlZCBiZWhhdmlvciByYXBpZGx5IHNhdHVyYXRlcywgcGVyaGFwcyBhcyBzb29uIGFzIGludmVyc2UgdGVtcGVyYXR1cmUgPSAtNS4KCmBgYHtyIHBsb3QtaWRlYWwtb2JzfQojfCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02CgpwbG90X2lkZWFsX29ic190cmlhbHMgPC0gcHJlZGljdGVkX2lkZWFsX29icyAlPiUKICBtdXRhdGUoCiAgICBzb2Z0bWF4X3RlbXBlcmF0dXJlID0gc3RyX2MoCiAgICAgICJJbnYuIHRlbXAuPS0iLAogICAgICBzdHJfcGFkKC1zb2Z0bWF4X3RlbXBlcmF0dXJlLCB3aWR0aCA9IDIsIHNpZGUgPSAibGVmdCIsIHBhZCA9ICIwIikKICAgICkKICApICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfY29ycmVjdCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfd3JhcCh+c29mdG1heF90ZW1wZXJhdHVyZSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpICsKICBzdGF0X3N1bW1hcnkoYWVzKGdyb3VwID0gMSksIGdlb20gPSAibGluZSIsIGZ1biA9IG1lYW4sIGxpbmV3aWR0aCA9IDEpICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkFjY3VyYWN5IiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAuNSwgMSkKICApICsKICBnZ3RpdGxlKCJTaW11bGF0ZWQgaWRlYWwgb2JzZXJ2ZXIiKQoKcGxvdF9pZGVhbF9vYnNfc3VtbWFyeSA8LSBwcmVkaWN0ZWRfaWRlYWxfb2JzICU+JQogIGdyb3VwX2J5KHNvZnRtYXhfdGVtcGVyYXR1cmUsIHNob3J0ZXN0X3BhdGgpICU+JQogIHN1bW1hcmlzZShwX2NvcnJlY3QgPSBtZWFuKHBfY29ycmVjdCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfY29ycmVjdCwgY29sb3I9c29mdG1heF90ZW1wZXJhdHVyZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKAogICAgbmFtZSA9ICJJbnYuIHRlbXAuIiwgb3B0aW9uID0gInR1cmJvIiwgZW5kID0gMC45LCBkaXJlY3Rpb24gPSAtMQogICkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQWNjdXJhY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMC41LCAxKQogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgZ2d0aXRsZSgiU2ltdWxhdGVkIGlkZWFsIG9ic2VydmVyIikKCnBsb3RfaWRlYWxfb2JzX3RyaWFscwpwbG90X2lkZWFsX29ic19zdW1tYXJ5CgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiaWRlYWxfb2JzX3RyaWFscy5wZGYiKSwKICAgIHBsb3QgPSBwbG90X2lkZWFsX29ic190cmlhbHMsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiaWRlYWxfb2JzX3N1bW1hcnkucGRmIiksCiAgICBwbG90ID0gcGxvdF9pZGVhbF9vYnNfc3VtbWFyeSwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKVGhpcyBpcyByZWZsZWN0ZWQgaW4gdGhlIGNvcnJlbGF0aW9uIG1hdHJpeC4gRm9yIHRoZSBwdXJwb3NlIG9mIHNpbXVsYXRpb24sIHdlJ2xsIGxpbWl0IHRoZSByYW5nZSB0byAtNS4KCmBgYHtyIGNvcnItaWRlYWwtb2JzfQojfCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04CgpwbG90X2lkZWFsX29ic19jb3JyIDwtIHByZWRpY3RlZF9pZGVhbF9vYnMgJT4lCiAgc2VsZWN0KHNvZnRtYXhfdGVtcGVyYXR1cmU6b3B0Ml9pZCwgcF9jb3JyZWN0KSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc29mdG1heF90ZW1wZXJhdHVyZSwgdmFsdWVzX2Zyb20gPSBwX2NvcnJlY3QpICU+JQogIHNlbGVjdCgtYyhzaG9ydGVzdF9wYXRoOm9wdDJfaWQpKSAlPiUKICBjb3IoKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJmcm9tIikgJT4lCiAgcGl2b3RfbG9uZ2VyKC1mcm9tLCBuYW1lc190byA9ICJ0byIsIHZhbHVlc190byA9ICJjb3JyIikgJT4lCiAgIyBQbG90dGluZwogIG11dGF0ZSgKICAgIGFjcm9zcyhjKGZyb20sIHRvKSwgYXMuaW50ZWdlciksCiAgICBhY3Jvc3MoYyhmcm9tLCB0byksIGZhY3RvciksCiAgICB0byA9IGZjdF9yZXYodG8pLAogICAgdGV4dCA9IHJvdW5kKGNvcnIsIDIpLAogICAgYWJvdmVfbWlkcG9pbnQgPSBjb3JyID4gMC41CiAgKSAlPiUKICBmaWx0ZXIoZnJvbSAhPSB0bykgJT4lCiAgZ2dwbG90KGFlcyh4PXRvLCB5PWZyb20sIGZpbGw9Y29ycikpICsKICB0aGVtZV9oZWF0bWFwKCkgKwogIGdlb21fdGlsZShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHRleHQsIGNvbG9yID0gYWJvdmVfbWlkcG9pbnQpLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MobGltaXRzID0gYygwLCAxKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJGQUxTRSI9IndoaXRlIiwgIlRSVUUiPSJibGFjayIpKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ2d0aXRsZSgKICAgICJJZGVhbCBvYnNlcnZlciBpbnZlcnNlIHRlbXBlcmF0dXJlIGNvcnJlbGF0aW9uIG1hdHJpeCIsCiAgICBzdWJ0aXRsZSA9ICIoZm9yIGJlaGF2aW9yIGFjcm9zcyBhbGwgZGlzdGFuY2VzKSIKICApCgpwbG90X2lkZWFsX29ic19jb3JyCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAiaWRlYWxfb2JzX2NvcnIucGRmIiksCiAgICBwbG90ID0gcGxvdF9pZGVhbF9vYnNfY29yciwKICAgIHdpZHRoID0gOCwgaGVpZ2h0ID0gOCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwCiAgKQp9CmBgYAoKIyMgU2ltdWxhdGUgYmVoYXZpb3IKCmBgYHtyIHNpbS1pZGVhbC1vYnN9CnNldC5zZWVkKHN1bSh1dGY4VG9JbnQoIk1heWJlIHRoZSBlbXB0aW5lc3MgaXMganVzdCBhIGxlc3NvbiBpbiBjYW52YXNlcyIpKSkKCnNpbV9wYXJhbXNfaWRlYWxfb2JzIDwtIHRpYmJsZSgKICBzdWJfaWQgPSAxOjUwMCwKICBzb2Z0bWF4X3RlbXBlcmF0dXJlID0gcnVuaWYobiA9IDUwMCwgbWluID0gLTUsIG1heCA9IDApCikKCnNpbV9iZWhhdl9pZGVhbF9vYnMgPC0gZXhwYW5kX2dyaWQoCiAgc3ViX2lkID0gMTo1MDAsCiAgbmF2X3RyaWFscwopICU+JQogICMgQWRkIHN1YmplY3Qtc3BlY2lmaWMgcGFyYW1ldGVycwogIGxlZnRfam9pbihzaW1fcGFyYW1zX2lkZWFsX29icywgYnkgPSBqb2luX2J5KHN1Yl9pZCkpICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBwX2Nob29zZV9vcHQxID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9kaXN0YW5jZSwgb3B0Ml9kaXN0YW5jZSksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSAxLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKSwKICAgIHNpbXVsYXRlZF9jaG9pY2UgPSBzYW1wbGUoCiAgICAgIGMob3B0MV9pZCwgb3B0Ml9pZCksIHNpemUgPSAxLCBwcm9iID0gYyhwX2Nob29zZV9vcHQxLCAxLXBfY2hvb3NlX29wdDEpCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKCnBsb3RfcGFyYW1fZGlzdF9pZGVhbF9vYnMgPC0gc2ltX3BhcmFtc19pZGVhbF9vYnMgJT4lCiAgZ2dwbG90KGFlcyh4PXNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KSArCiAgeGxhYigiSW52ZXJzZSB0ZW1wZXJhdHVyZSIpICsKICBnZ3RpdGxlKCJJZGVhbCBvYnNlcnZlcjogU2ltdWxhdGVkIHBhcmFtZXRlciBkaXN0cmlidXRpb24iKQoKcGxvdF9wYXJhbV9kaXN0X2lkZWFsX29icwoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgImlkZWFsX29ic19wYXJhbV9kaXN0LnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcGFyYW1fZGlzdF9pZGVhbF9vYnMsCiAgICB3aWR0aCA9IDUsIGhlaWdodCA9IDUsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBzaW1fYmVoYXZfaWRlYWxfb2JzICU+JQogICAgd3JpdGVfY3N2KAogICAgICBoZXJlKAogICAgICAgICJkYXRhIiwgInNpbXVsYXRlZF9tb2RlbF9iZWhhdmlvcnMiLAogICAgICAgICJzaW1fbmF2X2lkZWFsX29ic19ub19sYXBzZS5jc3YiCiAgICAgICkKICAgICkKfQpgYGAKCgojIFN1Y2Nlc3NvciBSZXByZXNlbnRhdGlvbgoKIyMgT3ZlcnZpZXcgYW5kIGNvbnRleHQKCkluIGEgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIHdvcmssIHdlIHVzZWQgYSB2ZXJzaW9uIG9mIHRoZSBTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24gKFNSKSB0aGF0IGluY2x1ZGVkIGEgbGFwc2UgcmF0ZSBwYXJhbWV0ZXIuIEFzIHdpdGggQkZTLWZvcndhcmQsIHRoZSBsYXBzZSByYXRlIHBhcmFtZXRlciBlbmRzIHVwIGJlaW5nIGhhcmQgdG8gcmVjb3ZlciwgYW5kIGJpYXNlcyB0aGUgZXN0aW1hdGlvbiBvZiBvdGhlciBwYXJhbWV0ZXJzLgoKSW4gdGhpcyB3b3JrLCB3ZSBvZnRlbiB3YW50IHRvIHNpbXVsYXRlIGFuICJhc3ltcHRvdGljIiBTdWNjZXNzb3IgUmVwcmVzZW50YXRpb24gKFNSKS4gSG93ZXZlciwgd2hlbiB1c2luZyBhIGRlbHRhLXJ1bGUgdXBkYXRpbmcgbWVjaGFuaXNtIHRvICJsZWFybiBmcm9tIG9ic2VydmF0aW9uIiwgdGhlcmUgd2lsbCBpbmV2aXRhYmx5IGJlIHNvbWUgc21hbGwgYW1vdW50IG9mIHN0b2NoYXN0aWNpdHkgYXNzb2NpYXRlZCB3aXRoIHRoZSBjaG9pY2Ugb2Ygb2JzZXJ2YXRpb25zLiBXZSBoYWQgcHJldmlvdXNseSB1c2VkIGEgZGVsdGEtcnVsZSBtZWNoYW5pc20gaW4gY29uanVuY3Rpb24gd2l0aCBhIHJlbGF0aXZlbHkgbGFyZ2UgbnVtYmVyIG9mIHNpbXVsYXRlZCBsZWFybmluZyBvYnNlcnZhdGlvbnMsIGJ1dCBvdXIgaW1wbGVtZW50YXRpb24gYWxsb3dlZCBmb3Igc3RvY2hhc3RpY2l0eSBpbiB0aGUgZXhhY3Qgb3JkZXJpbmcgb2YgdGhlc2Ugb2JzZXJ2YXRpb25zLCB3aGljaCAoaW4gcHJpbmNpcGxlKSByZXN1bHRzIGluIHRoZSBwcmVkaWN0ZWQgcmVwcmVzZW50YXRpb24gYmVpbmcgc2xpZ2h0bHkgZGlmZmVyZW50LgoKSW4gdGhlIHJldmlzaW9uLCB3ZSdsbCB0cnkgdHdvIHN0cmF0ZWdpZXMgZm9yIGdldHRpbmcgcmlkIG9mIHVubmVjZXNzYXJ5IHN0b2NoYXN0aWNpdHkgaW4gdGhlIHBhcmFtZXRlciBlc3RpbWF0aW9uIHByb2Nlc3M6IDEpIHVzaW5nIGEgY2xvc2VkLWZvcm0gYW5hbHl0aWMgc29sdXRpb24gdG8gY29tcHV0aW5nIFNScywgYW5kIDIpIHVzaW5nIGEgY29uc3RhbnQgc2V0IG9mIHNpbXVsYXRlZCAib2JzZXJ2YXRpb25zIiBzbyB0aGF0IHRoZSBkZWx0YS1ydWxlIFNSIGNvbnNpc3RlbnRseSBwcmVkaWN0cyB0aGUgc2FtZSByZXByZXNlbnRhdGlvbiAoZ2l2ZW4gdGhlIHNhbWUgcGFyYW1ldGVycykuIFRoZXJlIGFyZSwgdGhlb3JldGljYWxseSwgYmVuZWZpdHMgYW5kIGRyYXdiYWNrcyB0byBib3RoIGFwcHJvYWNoZXMsIHNvIHdlJ2xsIHdhbnQgdG8gdHJ5IHRoZW0gYm90aC4gVGhlIHNwb2lsZXIgYWxlcnQgaXMgdGhhdCwgYXQgbGVhc3QgZm9yIHRoaXMgd29yaywgYm90aCB2YXJpYW50cyBwcm9kdWNlIHNpbWlsYXIgZW5vdWdoIHBhdHRlcm5zIG9mIHJlc3VsdHMgdGhhdCBpdCBmdW5jdGlvbmFsbHkgZG9lc24ndCBtYXR0ZXIgd2hpY2ggaXMgdXNlZCAoaS5lLiwgdGhlIGludGVycHJldGF0aW9uIG9mIHJlc3VsdHMgaXMgaWRlbnRpY2FsKS4KCkZpbmFsbHksIFNSLWxpa2UgaW1wbGVtZW50YXRpb25zIG9mIG11bHRpc3RlcCBhYnN0cmFjdGlvbiBjYW4gZWl0aGVyIGJlIGNvbmNlcHR1YWxpemVkIGFzIGVuY29kaW5nIGluZm9ybWF0aW9uIGFib3V0IHRoZSAqbnVtYmVyKiBvZiB0aW1lcyBhbiBhZ2VudCBpcyBleHBlY3RlZCB0byBlbmQgdXAgaW4gYSBnaXZlbiBzdGF0ZSwgb3IgZWxzZSB0aGUgKnByb2JhYmlsaXR5KiBvZiBhbiBhZ2VudCBlbmRpbmcgdXAgaW4gdGhhdCBzdGF0ZS4gV2UgaGF2ZSBjaG9zZW4gdG8gdXNlIGFuIGltcGxlbWVudGF0aW9uIHRoYXQgZW5jb2RlcyBwcm9iYWJpbGl0aWVzLCBidXQgdGhlcmUgbWF5IGJlIGNvbmNlcm5zIHRoYXQgdGhpcyBpcyBhIHJlc2VhcmNoZXIgZGVncmVlIG9mIGZyZWVkb20uIFRvIGFkZHJlc3MgdGhpcyBjb25jZXJuLCB3ZSdsbCBhbHNvIGRlbW9uc3RyYXRlIGhlcmUgdGhhdCBib3RoIGltcGxlbWVudGF0aW9ucyBjb250YWluIGlkZW50aWNhbCBpbmZvcm1hdGlvbiwgYW5kIHRoYXQgaXQgaXMgdWx0aW1hdGVseSBpbmNvbnNlcXVlbnRpYWwgd2hpY2ggaW1wbGVtZW50YXRpb24gaXMgdXNlZC4KCiMjIEFuYWx5dGljIFNSCgpJbiBwYXN0IHJlc2VhcmNoLCBwZW9wbGUgaGF2ZSBnZW5lcmF0ZWQgYXN5bXB0b3RpYyBTUnMgdXNpbmcgdGhlIGZvbGxvd2luZyBjbG9zZWQtZm9ybSBhbmFseXRpYyBzb2x1dGlvbjoKCiRNID0gKEkgLSBcZ2FtbWEgVCleey0xfSQKCndoZXJlICRNJCBpcyB0aGUgU1IgbWF0cml4LCAkSSQgaXMgdGhlIGlkZW50aXR5IG1hdHJpeCwgJFxnYW1tYSQgaXMgdGhlIHN1Y2Nlc3NvciBob3Jpem9uL2Rpc2NvdW50LCAkVCQgaXMgdGhlIHRydWUgdHJhbnNpdGlvbiBtYXRyaXgsIGFuZCB3aGVyZSAkWF57LTF9JCByZWZlcnMgdG8gdGhlIG1hdHJpeCBpbnZlcnNlLgoKTm90ZSB0aGF0IHRoZSAib3V0LW9mLXRoZS1ib3giIFNSIHByb2R1Y2VzIGEgbWF0cml4IG9mIGNvdW50cyAobW9yZSB0ZWNobmljYWxseSwgImV4cGVjdGVkIGRpc2NvdW50ZWQgZnV0dXJlIHZpc2l0YXRpb25zIiwgYnV0IHRoaXMgaXMgcXVpdGUgdGhlIG1vdXRoZnVsKS4gU3BlY2lmaWNhbGx5LCB0aGUgY291bnRzIHF1YW50aWZ5ICJpZiBJIHN0YXJ0IGF0IHN0YXRlIFggYW5kIHRha2UgYSByYW5kb20gd2FsayBvZiBsZW5ndGggTCwgaG93IG1hbnkgdGltZXMgc2hvdWxkIEkgZXhwZWN0IHRvIGVuZCB1cCBpbiBzdGF0ZSBZPyIgVGhlIHRlcm0gJEwkIGNhbiBiZSB0aG91Z2h0IG9mIGFzIGEgImxvb2thaGVhZCIgaG9yaXpvbiwgYW5kIGlzIHJlbGF0ZWQgdG8gJFxnYW1tYSQgdGhyb3VnaCB0aGUgZm9sbG93aW5nIGVxdWF0aW9uczogJEwgPSBcZnJhY3sxfXsxLVxnYW1tYX0kIGFuZCAkXGdhbW1hID0gMSAtIFxmcmFjezF9e0x9JC4gVGhlcmVmb3JlLCB0aGUgU1IgY2FuIGJlIG5vcm1hbGl6ZWQgYXMgYSBtYXRyaXggb2YgcHJvYmFiaWxpdGllczogJE1fe1x0ZXh0e3Byb2JhYmlsaXRpZXN9fSBcbGVmdGFycm93IE1fe1x0ZXh0e2NvdW50c319IFx0aW1lcyBcZnJhY3sxfXtMfSQsIHdoaWNoIHF1YW50aWZ5ICJpZiBJIHN0YXJ0IGF0IHN0YXRlIFggYW5kIHRha2UgYSByYW5kb20gd2FsayBvZiBsZW5ndGggTCwgd2hhdCdzIHRoZSBwcm9iYWJpbGl0eSBvZiBtZSBlbmRpbmcgdXAgaW4gc3RhdGUgWT8iLgoKSW4gYSBtb21lbnQsIHdlJ2xsIHZlcmlmeSB0aGF0IHRoZSBjb3VudCBhbmQgcHJvYmFiaWxpdHkgbWF0cmljZXMgY29udGFpbiB0aGUgc2FtZSBpbmZvcm1hdGlvbiBhbmQgcmVzdWx0IGluIGlkZW50aWNhbCBwcmVkaWN0ZWQgYmVoYXZpb3JzIGluIHRoZSBzb2NpYWwgbmF2aWdhdGlvbiB0YXNrLgoKYGBge3IgYW5hbHl0aWMtc3ItcmVwfQpzcl9hbmFseXRpY19jb3VudHMgPC0gbWFwX2RmcigKICAueCA9IHJvdW5kKHNlcSgwLjEsIDAuOSwgMC4xKSwgMiksCiAgLmYgPSB+YnVpbGRfc3VjY2Vzc29yX2FuYWx5dGljYWxseSgKICAgIHRyYW5zaXRpb25fbWF0cml4ID0gdHJhbnNtYXQsCiAgICBzdWNjZXNzb3JfaG9yaXpvbiA9IC54LAogICAgbm9ybWFsaXplID0gRkFMU0UKICApCikKCnNyX2FuYWx5dGljX3Byb2JzIDwtIG1hcF9kZnIoCiAgLnggPSByb3VuZChzZXEoMC4xLCAwLjksIDAuMSksIDIpLAogIC5mID0gfmJ1aWxkX3N1Y2Nlc3Nvcl9hbmFseXRpY2FsbHkoCiAgICB0cmFuc2l0aW9uX21hdHJpeCA9IHRyYW5zbWF0LAogICAgc3VjY2Vzc29yX2hvcml6b24gPSAueCwKICAgIG5vcm1hbGl6ZSA9IFRSVUUKICApCikKYGBgCgpXaGlsZSBpdCdzIG5pY2UgdG8gaGF2ZSBzb21lIHRoZW9yZXRpY2FsIGd1YXJhbnRlZXMgYWJvdXQgYXN5bXB0b3RpYyByZXByZXNlbnRhdGlvbiwgYXMgd2VsbCBhcyBhbiBlbGVnYW50IGNsb3NlZC1mb3JtIGVxdWF0aW9uLCB0aGlzIGlzIGZhciBmcm9tIGJlaW5nIGFuIGFzc3VtcHRpb24tZnJlZSBzb2x1dGlvbi4gU3BlY2lmaWNhbGx5LCB0aGUgYW5hbHl0aWMgc29sdXRpb24gYXNzdW1lcyB0aGF0IHRoZSBzdHJvbmdlc3QgY29udHJpYnV0b3IgdG8gcmVwcmVzZW50YXRpb24gaXMgdGhlIGlkZW50aXR5IG1hdHJpeCAoaS5lLiwgc3RhdGUgWCB0cmFuc2l0aW9uaW5nIHRvIGl0c2VsZikuIFRoaXMgYmVjb21lcyBjbGVhcmVyIHdoZW4gd3JpdGluZyBvdXQgdGhlIGFuYWx5dGljIHNvbHV0aW9uIGFzIGEgc3VtbWF0aW9uOgoKJE0gPSBcc3VtX3trPTB9XlxpbmZ0eSBcZ2FtbWFeayBUXmskCgpBcyB0aGUgJFxnYW1tYV5rJCB0ZXJtIHNwZWNpZmllcyB0aGUgZXhwb25lbnRpYWwgZGlzY291bnQvZGVjYXkgZmFjdG9yLCAkXGdhbW1hXjAgVF4wID0gSSQgaXMgcXVpdGUgbGl0ZXJhbGx5IHRoZSBtb3N0IGhlYXZpbHktd2VpZ2h0ZWQgdGVybSBpbiB0aGUgc3VtbWF0aW9uLgoKV2UgY2FuIHNlZSB0aGlzIHByZXR0eSBjbGVhcmx5IHdoZW4gcGxvdHRpbmcgdGhlIHByZWRpY3RlZCByZXByZXNlbnRhdGlvbnM6CgpgYGB7ciBwbG90LWFuYWx5dGljLXNyLXJlcH0KI3wgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NgoKcGxvdF9zcl9hbmFseXRpY19jb3VudHMgPC0gc3JfYW5hbHl0aWNfY291bnRzICU+JQogIG11dGF0ZSgKICAgIGFjcm9zcyhjKGZyb20sIHRvKSwgZmFjdG9yKSwKICAgIGZyb20gPSBmY3RfcmV2KGZyb20pLAogICAgc3JfZ2FtbWEgPSBzdHJfYyh1bmljb2RlX2dyZWVrWyJnYW1tYSJdLCAiPSIsIHNyX2dhbW1hKQogICkgJT4lCiAgZ2dwbG90KGFlcyh4PXRvLCB5PWZyb20sIGZpbGw9c3JfdmFsdWUpKSArCiAgdGhlbWVfaGVhdG1hcCgpICsKICBmYWNldF93cmFwKH5zcl9nYW1tYSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lID0gIlNSIGNvdW50cyIpICsKICBjb29yZF9maXhlZCgpICsKICBnZ3RpdGxlKCJDb3VudCBtYXRyaXgiKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpCiAgKQoKcGxvdF9zcl9hbmFseXRpY19wcm9icyA8LSBzcl9hbmFseXRpY19wcm9icyAlPiUKICBtdXRhdGUoCiAgICBhY3Jvc3MoYyhmcm9tLCB0byksIGZhY3RvciksCiAgICBmcm9tID0gZmN0X3Jldihmcm9tKSwKICAgIHNyX2dhbW1hID0gc3RyX2ModW5pY29kZV9ncmVla1siZ2FtbWEiXSwgIj0iLCBzcl9nYW1tYSkKICApICU+JQogIGdncGxvdChhZXMoeD10bywgeT1mcm9tLCBmaWxsPXNyX3ZhbHVlKSkgKwogIHRoZW1lX2hlYXRtYXAoKSArCiAgZmFjZXRfd3JhcCh+c3JfZ2FtbWEpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MobmFtZSA9ICJTUiBwcm9iYWJpbGl0aWVzIikgKwogIGNvb3JkX2ZpeGVkKCkgKwogIGdndGl0bGUoIlByb2JhYmlsaXR5IG1hdHJpeCIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkKICApCgpwbG90X3NyX2FuYWx5dGljX3JlcCA8LSB3cmFwX3Bsb3RzKAogIHBsb3Rfc3JfYW5hbHl0aWNfY291bnRzLCBwbG90X3NyX2FuYWx5dGljX3Byb2JzLAogIG5yb3cgPSAxCikgJgogIHBsb3RfYW5ub3RhdGlvbigKICAgIHRpdGxlID0gIkFuYWx5dGljIFNSIiwKICAgIHRoZW1lID0gdGhlbWUoCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICAgKQogICkKCnBsb3Rfc3JfYW5hbHl0aWNfcmVwCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfYW5hbHl0aWNfcmVwLnBkZiIpLAogICAgcGxvdCA9IHBsb3Rfc3JfYW5hbHl0aWNfcmVwLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA2LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAsCiAgICBkZXZpY2UgPSBjYWlyb19wZGYKICApCn0KYGBgCgpBdCBmaXJzdCBnbGFuY2UsIGl0IGxvb2tzIGxpa2UgdGhlIGNvdW50IGFuZCBwcm9iYWJpbGl0eSBtYXRyaWNlcyBtaWdodCBjb250YWluIGRpZmZlcmVudCBpbmZvcm1hdGlvbi4gSG93ZXZlciwgdGhleSBhcmUgZnVuZGFtZW50YWxseSB0aGUgc2FtZS4gTGV0J3MgdmVyaWZ5IHRoYXQgdGhleSBtYWtlICpleGFjdGx5KiB0aGUgc2FtZSBjb250cmlidXRpb25zIHRvIGJlaGF2aW9yIHdoZW4gdGhlIHNvZnRtYXggKGludmVyc2UpIHRlbXBlcmF0dXJlIHBhcmFtZXRlciBhY2NvdW50cyBmb3IgdGhlIGZhY3QgdGhhdCB0aGUgcHJvYmFiaWxpdHkgbWF0cml4IGlzIG5vcm1hbGl6ZWQgYnkgdGhlIGxvb2thaGVhZC4gSGVyZSwgdGhlIHRlbXBlcmF0dXJlIGZvciB0aGUgY291bnQgbWF0cml4IGlzICQxMCQsIGFuZCB0aGUgdGVtcGVyYXR1cmUgZm9yIHRoZSBwcm9iYWJpbGl0eSBtYXRyaXggaXMgJDEwTCA9IDEwIFx0aW1lcyBcZnJhY3sxfXsxLVxnYW1tYX0kLgoKYGBge3IgYW5hbHl0aWMtc3ItcHJlZGljdGVkfQpwcmVkaWN0ZWRfc3JfYW5hbHl0aWNfY291bnRzIDwtIGV4cGFuZF9ncmlkKAogIHNyX2dhbW1hID0gcm91bmQoc2VxKDAuMSwgMC45LCAwLjEpLCAyKSwKICBuYXZfdHJpYWxzCikgJT4lCiAgbGVmdF9qb2luKAogICAgc3JfYW5hbHl0aWNfY291bnRzICU+JQogICAgICByZW5hbWUoZW5kcG9pbnRfaWQgPSB0bywgb3B0MV9pZCA9IGZyb20sIG9wdDFfc3IgPSBzcl92YWx1ZSksCiAgICBieSA9IGpvaW5fYnkoc3JfZ2FtbWEsIGVuZHBvaW50X2lkLCBvcHQxX2lkKQogICkgJT4lCiAgbGVmdF9qb2luKAogICAgc3JfYW5hbHl0aWNfY291bnRzICU+JQogICAgICByZW5hbWUoZW5kcG9pbnRfaWQgPSB0bywgb3B0Ml9pZCA9IGZyb20sIG9wdDJfc3IgPSBzcl92YWx1ZSksCiAgICBieSA9IGpvaW5fYnkoc3JfZ2FtbWEsIGVuZHBvaW50X2lkLCBvcHQyX2lkKQogICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHBfY29ycmVjdCA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKG9wdDFfc3IsIG9wdDJfc3IpLAogICAgICBvcHRpb25fY2hvc2VuID0gaWZfZWxzZShvcHQxX2lkID09IGNvcnJlY3RfY2hvaWNlLCAxLCAyKSwKICAgICAgdGVtcGVyYXR1cmUgPSAxMCwKICAgICAgdXNlX2ludmVyc2VfdGVtcGVyYXR1cmUgPSBUUlVFCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKCnByZWRpY3RlZF9zcl9hbmFseXRpY19wcm9icyA8LSBleHBhbmRfZ3JpZCgKICBzcl9nYW1tYSA9IHJvdW5kKHNlcSgwLjEsIDAuOSwgMC4xKSwgMiksCiAgbmF2X3RyaWFscwopICU+JQogIGxlZnRfam9pbigKICAgIHNyX2FuYWx5dGljX3Byb2JzICU+JQogICAgICByZW5hbWUoZW5kcG9pbnRfaWQgPSB0bywgb3B0MV9pZCA9IGZyb20sIG9wdDFfc3IgPSBzcl92YWx1ZSksCiAgICBieSA9IGpvaW5fYnkoc3JfZ2FtbWEsIGVuZHBvaW50X2lkLCBvcHQxX2lkKQogICkgJT4lCiAgbGVmdF9qb2luKAogICAgc3JfYW5hbHl0aWNfcHJvYnMgJT4lCiAgICAgIHJlbmFtZShlbmRwb2ludF9pZCA9IHRvLCBvcHQyX2lkID0gZnJvbSwgb3B0Ml9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzcl9nYW1tYSwgZW5kcG9pbnRfaWQsIG9wdDJfaWQpCiAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgbG9va2FoZWFkID0gMS8oMS1zcl9nYW1tYSksCiAgICBwX2NvcnJlY3QgPSBzb2Z0bWF4KAogICAgICBvcHRpb25fdmFsdWVzID0gYyhvcHQxX3NyLCBvcHQyX3NyKSwKICAgICAgb3B0aW9uX2Nob3NlbiA9IGlmX2Vsc2Uob3B0MV9pZCA9PSBjb3JyZWN0X2Nob2ljZSwgMSwgMiksCiAgICAgIHRlbXBlcmF0dXJlID0gMTAgKiBsb29rYWhlYWQsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZXJlJ3MgYSBwZXJmZWN0IGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGJlaGF2aW9yYWwgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgY291bnQtIGFuZCBwcm9iYWJpbGl0eS1iYXNlZCBtYXRyaWNlcy4KCmBgYHtyIGNvcnItYW5hbHl0aWMtc3ItcHJlZGljdGVkfQpsZWZ0X2pvaW4oCiAgcHJlZGljdGVkX3NyX2FuYWx5dGljX2NvdW50cyAlPiUKICAgIHNlbGVjdCgKICAgICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsCiAgICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgICBvcHQxX2lkLCBvcHQyX2lkLAogICAgICBwX2NvcnJlY3RfY291bnRzID0gcF9jb3JyZWN0CiAgICApLAogIHByZWRpY3RlZF9zcl9hbmFseXRpY19wcm9icyAlPiUKICAgIHNlbGVjdCgKICAgICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsCiAgICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgICBvcHQxX2lkLCBvcHQyX2lkLAogICAgICBwX2NvcnJlY3RfcHJvYnMgPSBwX2NvcnJlY3QKICAgICksCiAgYnkgPSBqb2luX2J5KAogICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgKQopICU+JQogIGdyb3VwX2J5KHNyX2dhbW1hKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKAogICAgY29yciA9IG1hcF9kYmwoCiAgICAgIC54ID0gZGF0YSwKICAgICAgLmYgPSB+d2l0aCgueCwgY29yKHBfY29ycmVjdF9jb3VudHMsIHBfY29ycmVjdF9wcm9icykpCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KC1kYXRhKSAlPiUKICBrYWJsZV9jdXN0b20oCiAgICBjYXB0aW9ucyA9IGMoCiAgICAgICJDb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0ZWQgYmVoYXZpb3IgZnJvbSBhbmFseXRpYyBTUnMiLAogICAgICAiKGNvdW50cyB2cyBwcm9iYWJpbGl0aWVzKSIKICAgICkKICApCmBgYAoKQW5kIGp1c3QgdG8gdmlzdWFsbHkgY29uZmlybSwgd2UgY2FuIHBsb3Qgb3V0IHRoZSBtb2RlbCBwcmVkaWN0aW9ucyB0byBjb25maXJtIHRoYXQgdGhlICJtb25vdG9uaWMgcmVsYXRpb25zaGlwIiBiZWluZyBpbmRleGVkIGJ5IHRoZSBjb3JyZWxhdGlvbiBpcyBhY3R1YWxseSAiaWRlbnRpdHkiLgoKYGBge3IgcGxvdC1hbmFseXRpYy1zcn0KI3wgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OAoKcGxvdF9zcl9hbmFseXRpY19uYXYgPC0gbGVmdF9qb2luKAogIHByZWRpY3RlZF9zcl9hbmFseXRpY19jb3VudHMgJT4lCiAgICBzZWxlY3QoCiAgICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLAogICAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgcF9jb3JyZWN0X2NvdW50cyA9IHBfY29ycmVjdAogICAgKSwKICBwcmVkaWN0ZWRfc3JfYW5hbHl0aWNfcHJvYnMgJT4lCiAgICBzZWxlY3QoCiAgICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLAogICAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgcF9jb3JyZWN0X3Byb2JzID0gcF9jb3JyZWN0CiAgICApLAogIGJ5ID0gam9pbl9ieSgKICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICkKKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoInBfY29ycmVjdF8iKSwKICAgIG5hbWVzX3RvID0gIm1ldGhvZCIsCiAgICB2YWx1ZXNfdG8gPSAicF9jb3JyZWN0IgogICkgJT4lCiAgbXV0YXRlKAogICAgbWV0aG9kID0gc3RyX3JlbW92ZShtZXRob2QsICJwX2NvcnJlY3RfIiksCiAgICBzaG9ydGVzdF9wYXRoID0gc3RyX2MoIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UgIiwgc2hvcnRlc3RfcGF0aCksCiAgICBzcl9nYW1tYSA9IHN0cl9jKHVuaWNvZGVfZ3JlZWtbImdhbW1hIl0sICI9Iiwgc3JfZ2FtbWEpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCB5PXBfY29ycmVjdCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfZ3JpZCgKICAgIHJvd3MgPSB2YXJzKHNyX2dhbW1hKSwKICAgIGNvbHMgPSB2YXJzKHNob3J0ZXN0X3BhdGgpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gaW50ZXJhY3Rpb24oc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQpKSwKICAgIGFscGhhID0gMC4xCiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSAiTm9ybWFsaXphdGlvbiBtZXRob2QiLAogICAgbGFiZWxzID0gYygiY291bnRzIj0iQ291bnRzIiwgInByb2JzIj0iUHJvYmFiaWxpdGllcyIpCiAgKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsCiAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsCiAgICBicmVha3MgPSBzZXEoMC41LCAxLCAwLjI1KQogICkgKwogIGdndGl0bGUoIlByZWRpY3RlZCBuYXZpZ2F0aW9uIGFjY3VyYWN5OiBBbmFseXRpYyBTUiIpCgpwbG90X3NyX2FuYWx5dGljX25hdgoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJvdXRwdXRzIiwgd29ya2Zsb3dfbmFtZSwgInNyX2FuYWx5dGljX2NvdW50X3ZzX3Byb2IucGRmIiksCiAgICBwbG90ID0gcGxvdF9zcl9hbmFseXRpY19uYXYsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDgsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMCwKICAgIGRldmljZSA9IGNhaXJvX3BkZgogICkKfQpgYGAKCiMjIERlbHRhLXJ1bGUgU1IKCkhvdyBkb2VzIHRoZSAiZGVsdGEtcnVsZSIgdmVyc2lvbiBvZiB0aGUgU1IgY29tcGFyZT8gTGV0J3MgZ2VuZXJhdGUgYSBidW5jaCBvZiBzaW11bGF0ZWQgIm9ic2VydmF0aW9ucyIuIFJhbmRvbSB3YWxrcyBjYW4gKGFuZCBvZnRlbiBkbykgZ2V0IHN0dWNrIHdpdGhpbiBjbHVzdGVycy9jb21tdW5pdGllcywgc28gc2luY2Ugd2UncmUgZXhwbGljaXRseSBpbnRlcmVzdGVkIGluIGFzeW1wdG90aWMgcmVwcmVzZW50YXRpb25zLCBsZXQncyBzaW11bGF0ZSBhIGJ1bmNoIG9mICJwYWlyZWQgYXNzb2NpYXRlcyIgc3VjaCB0aGF0IHdlIGN5Y2xlIHRocm91Z2ggYWxsIHBhaXJzIChpbiByYW5kb20gb3JkZXIpIGJlZm9yZSBjeWNsaW5nIHRocm91Z2ggYWxsIHBhaXJzIGFnYWluLgoKTm90ZSB0aGF0IHdlJ2xsIGdlbmVyYXRlIG1vcmUgb2JzZXJ2YXRpb25zIHRoYW4gd2Ugc3RyaWN0bHkgbmVlZCwganVzdCBzbyB0aGF0IHdlIGhhdmUgdGhlbSBvbiBoYW5kLiBOb3RlIGFsc28gdGhhdCB3ZSdsbCBzZXQgYSByYW5kb20gc2VlZCBpbiB0aGUgbmV4dCBjb2RlIGNlbGwgdG8gZW5zdXJlIHRoYXQgd2UgYWx3YXlzIGdlbmVyYXRlIHRoZSBzYW1lIG9ic2VydmF0aW9ucy4KCmBgYHtyIGdlbmVyYXRlLW9ic2VydmF0aW9uc30Kc2V0LnNlZWQoc3VtKHV0ZjhUb0ludCgiV2F0Y2ggdGhlbSwgdGFrZSBpdCBvbiBiYWNrLCBkbyB0aGUgcmV3aW5kIikpKQoKb2JzX2Zvcl9zcl9kZWx0YV9ydWxlIDwtIGV4cGFuZF9ncmlkKAogIGl0ZXIgPSAxOjUwMDAsCiAgYWRqbGlzdCAlPiUKICAgIGZpbHRlcihlZGdlID09IDEpICU+JQogICAgc2VsZWN0KGZyb20sIHRvKQopICU+JQogIGdyb3VwX2J5KGl0ZXIpICU+JQogIHNsaWNlX3NhbXBsZShwcm9wID0gMSkgJT4lCiAgdW5ncm91cCgpCgppZiAoa25pdHRpbmcpIHsKICBoZXJlKCJkYXRhIiwgInNyX29icyIpICU+JQogICAgY3JlYXRlX3BhdGgoKQogIAogIG9ic19mb3Jfc3JfZGVsdGFfcnVsZSAlPiUKICAgIHdyaXRlX2NzdihmaWxlID0gaGVyZSgiZGF0YSIsICJzcl9vYnMiLCAic2ltX29ic19mb3Jfc3IuY3N2IikpCn0KYGBgCgpMZXQncyBzaW11bGF0ZSBTUnMgdXNpbmcgYSBkaWZmZXJlbnQgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBlYWNoIHRpbWUuIE5vdGUgdGhhdCB3ZSdyZSB1c2luZyAiYmlkaXJlY3Rpb25hbCIgdXBkYXRpbmcsIG1lYW5pbmcgdGhhdCBvYnNlcnZpbmcgdGhlIHBhaXIgQStCIHRyaWdnZXJzIGEgbGVhcm5pbmcgdXBkYXRlIGZvciBib3RoIEEgYW5kIEIuCgpgYGB7ciBkZWx0YS1ydWxlLXNyLXJlcH0Kc3JfZGVsdGFfMTAwIDwtIG1hcF9kZnIoCiAgLnggPSByb3VuZChzZXEoMC4xLCAwLjksIDAuMSksIDIpLAogIC5mID0gfmJ1aWxkX3N1Y2Nlc3Nvcl90ZF8wKAogICAgc3VjY2Vzc29yX21hdHJpeCA9IGRpYWcobnJvdyA9IDEzLCBuY29sID0gMTMpLAogICAgb2JzZXJ2YXRpb25fbWF0cml4ID0gb2JzX2Zvcl9zcl9kZWx0YV9ydWxlICU+JQogICAgICBmaWx0ZXIoaXRlciA8PSAxMDApICU+JQogICAgICBzZWxlY3QoZnJvbSwgdG8pICU+JQogICAgICBhcy5tYXRyaXgoKSwKICAgIHNyX2FscGhhID0gMC4xLAogICAgc3JfZ2FtbWEgPSAueCwKICAgIGJpZGlyZWN0aW9uYWwgPSBUUlVFCiAgKQopCgpzcl9kZWx0YV81MDAwIDwtIG1hcF9kZnIoCiAgLnggPSByb3VuZChzZXEoMC4xLCAwLjksIDAuMSksIDIpLAogIC5mID0gfmJ1aWxkX3N1Y2Nlc3Nvcl90ZF8wKAogICAgc3VjY2Vzc29yX21hdHJpeCA9IGRpYWcobnJvdyA9IDEzLCBuY29sID0gMTMpLAogICAgb2JzZXJ2YXRpb25fbWF0cml4ID0gb2JzX2Zvcl9zcl9kZWx0YV9ydWxlICU+JQogICAgICBmaWx0ZXIoaXRlciA8PSA1MDAwKSAlPiUKICAgICAgc2VsZWN0KGZyb20sIHRvKSAlPiUKICAgICAgYXMubWF0cml4KCksCiAgICBzcl9hbHBoYSA9IDAuMSwKICAgIHNyX2dhbW1hID0gLngsCiAgICBiaWRpcmVjdGlvbmFsID0gVFJVRQogICkKKQpgYGAKCkl0IHNlZW1zIGZyb20gdGhlIHByZWRpY3RlZCByZXByZXNlbnRhdGlvbnMgdGhhdCB0aGVyZSBpc24ndCBtdWNoIG9mIGEgZGlmZmVyZW5jZSBiZXR3ZWVuIHVzaW5nIDEwMCBvYnNlcnZhdGlvbnMgdnMgNTAwMCBvYnNlcnZhdGlvbnMuCgpgYGB7ciBwbG90LWRlbHRhLXJ1bGUtc3ItcmVwfQojfCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02CgpwbG90X3NyX2RlbHRhXzEwMCA8LSBzcl9kZWx0YV8xMDAgJT4lCiAgbXV0YXRlKAogICAgYWNyb3NzKGMoZnJvbSwgdG8pLCBmYWN0b3IpLAogICAgZnJvbSA9IGZjdF9yZXYoZnJvbSksCiAgICBzcl9nYW1tYSA9IHN0cl9jKHVuaWNvZGVfZ3JlZWtbImdhbW1hIl0sICI9Iiwgc3JfZ2FtbWEpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dG8sIHk9ZnJvbSwgZmlsbD1zcl92YWx1ZSkpICsKICB0aGVtZV9oZWF0bWFwKCkgKwogIGZhY2V0X3dyYXAofnNyX2dhbW1hKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG5hbWUgPSAiU1IgY291bnRzIiwgbGltaXRzID0gYygwLCAyLjUpKSArCiAgY29vcmRfZml4ZWQoKSArCiAgZ2d0aXRsZSgiMTAwIG9ic2VydmF0aW9ucyIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkKICApCgpwbG90X3NyX2RlbHRhXzUwMDAgPC0gc3JfZGVsdGFfNTAwMCAlPiUKICBtdXRhdGUoCiAgICBhY3Jvc3MoYyhmcm9tLCB0byksIGZhY3RvciksCiAgICBmcm9tID0gZmN0X3Jldihmcm9tKSwKICAgIHNyX2dhbW1hID0gc3RyX2ModW5pY29kZV9ncmVla1siZ2FtbWEiXSwgIj0iLCBzcl9nYW1tYSkKICApICU+JQogIGdncGxvdChhZXMoeD10bywgeT1mcm9tLCBmaWxsPXNyX3ZhbHVlKSkgKwogIHRoZW1lX2hlYXRtYXAoKSArCiAgZmFjZXRfd3JhcCh+c3JfZ2FtbWEpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MobmFtZSA9ICJTUiBjb3VudHMiLCBsaW1pdHMgPSBjKDAsIDIuNSkpICsKICBjb29yZF9maXhlZCgpICsKICBnZ3RpdGxlKCI1MDAwIG9ic2VydmF0aW9ucyIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkKICApCgpwbG90X3NyX2RlbHRhX3JlcCA8LSB3cmFwX3Bsb3RzKAogIHBsb3Rfc3JfZGVsdGFfMTAwLCBwbG90X3NyX2RlbHRhXzUwMDAsCiAgbnJvdyA9IDEsIGd1aWRlcyA9ICJjb2xsZWN0IgopICYKICBwbG90X2Fubm90YXRpb24oCiAgICB0aXRsZSA9ICJEZWx0YS1ydWxlIFNSIiwKICAgIHRoZW1lID0gdGhlbWUoCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICAgKQogICkKCnBsb3Rfc3JfZGVsdGFfcmVwCgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfZGVsdGFfcnVsZV9yZXAucGRmIiksCiAgICBwbG90ID0gcGxvdF9zcl9kZWx0YV9yZXAsCiAgICB3aWR0aCA9IDgsIGhlaWdodCA9IDYsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMCwKICAgIGRldmljZSA9IGNhaXJvX3BkZgogICkKfQpgYGAKCldlIGNhbiBhbHNvIGNoZWNrIHdoZXRoZXIgdGhlIDEwMC0gdnMgNTAwMC1vYnNlcnZhdGlvbiBTUnMgbWFrZSBlc3NlbnRpYWxseSB0aGUgc2FtZSBwcmVkaWN0aW9ucyBhYm91dCBiZWhhdmlvci4KCmBgYHtyIGRlbHRhLXJ1bGUtc3ItcHJlZGljdGVkfQpwcmVkaWN0ZWRfc3JfZGVsdGFfMTAwIDwtIGV4cGFuZF9ncmlkKAogIHNyX2dhbW1hID0gcm91bmQoc2VxKDAuMSwgMC45LCAwLjEpLCAyKSwKICBuYXZfdHJpYWxzCikgJT4lCiAgbGVmdF9qb2luKAogICAgc3JfZGVsdGFfMTAwICU+JQogICAgICBzZWxlY3Qoc3JfZ2FtbWEsIGVuZHBvaW50X2lkID0gdG8sIG9wdDFfaWQgPSBmcm9tLCBvcHQxX3NyID0gc3JfdmFsdWUpLAogICAgYnkgPSBqb2luX2J5KHNyX2dhbW1hLCBlbmRwb2ludF9pZCwgb3B0MV9pZCkKICApICU+JQogIGxlZnRfam9pbigKICAgIHNyX2RlbHRhXzEwMCAlPiUKICAgICAgc2VsZWN0KHNyX2dhbW1hLCBlbmRwb2ludF9pZCA9IHRvLCBvcHQyX2lkID0gZnJvbSwgb3B0Ml9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzcl9nYW1tYSwgZW5kcG9pbnRfaWQsIG9wdDJfaWQpCiAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKG9wdDFfaWQgPT0gY29ycmVjdF9jaG9pY2UsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IDEwLAogICAgICB1c2VfaW52ZXJzZV90ZW1wZXJhdHVyZSA9IFRSVUUKICAgICkKICApICU+JQogIHVuZ3JvdXAoKQoKcHJlZGljdGVkX3NyX2RlbHRhXzUwMDAgPC0gZXhwYW5kX2dyaWQoCiAgc3JfZ2FtbWEgPSByb3VuZChzZXEoMC4xLCAwLjksIDAuMSksIDIpLAogIG5hdl90cmlhbHMKKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBzcl9kZWx0YV81MDAwICU+JQogICAgICBzZWxlY3Qoc3JfZ2FtbWEsIGVuZHBvaW50X2lkID0gdG8sIG9wdDFfaWQgPSBmcm9tLCBvcHQxX3NyID0gc3JfdmFsdWUpLAogICAgYnkgPSBqb2luX2J5KHNyX2dhbW1hLCBlbmRwb2ludF9pZCwgb3B0MV9pZCkKICApICU+JQogIGxlZnRfam9pbigKICAgIHNyX2RlbHRhXzUwMDAgJT4lCiAgICAgIHNlbGVjdChzcl9nYW1tYSwgZW5kcG9pbnRfaWQgPSB0bywgb3B0Ml9pZCA9IGZyb20sIG9wdDJfc3IgPSBzcl92YWx1ZSksCiAgICBieSA9IGpvaW5fYnkoc3JfZ2FtbWEsIGVuZHBvaW50X2lkLCBvcHQyX2lkKQogICkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSgKICAgIHBfY29ycmVjdCA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKG9wdDFfc3IsIG9wdDJfc3IpLAogICAgICBvcHRpb25fY2hvc2VuID0gaWZfZWxzZShvcHQxX2lkID09IGNvcnJlY3RfY2hvaWNlLCAxLCAyKSwKICAgICAgdGVtcGVyYXR1cmUgPSAxMCwKICAgICAgdXNlX2ludmVyc2VfdGVtcGVyYXR1cmUgPSBUUlVFCiAgICApCiAgKSAlPiUKICB1bmdyb3VwKCkKYGBgCgpXZSBzZWUgdGhhdCB0aGVyZSBhcmUgaGlnaCBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGUgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgMTAwLSB2cyA1MDAwLW9ic2VydmF0aW9uIFNSczoKCmBgYHtyIGNvcnItZGVsdGEtcnVsZS1zci1wcmVkaWN0ZWR9CmxlZnRfam9pbigKICBwcmVkaWN0ZWRfc3JfZGVsdGFfMTAwICU+JQogICAgc2VsZWN0KAogICAgICBzcl9nYW1tYSwgc2hvcnRlc3RfcGF0aCwKICAgICAgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsCiAgICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICAgIHNyXzEwMCA9IHBfY29ycmVjdAogICAgKSwKICBwcmVkaWN0ZWRfc3JfZGVsdGFfNTAwMCAlPiUKICAgIHNlbGVjdCgKICAgICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsCiAgICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgICBvcHQxX2lkLCBvcHQyX2lkLAogICAgICBzcl81MDAwID0gcF9jb3JyZWN0CiAgICApLAogIGJ5ID0gam9pbl9ieSgKICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICkKKSAlPiUKICBncm91cF9ieShzcl9nYW1tYSkgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZSgKICAgIGNvcnIgPSBtYXBfZGJsKC54ID0gZGF0YSwgLmYgPSB+d2l0aCgueCwgY29yKHNyXzEwMCwgc3JfNTAwMCkpKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdCgtZGF0YSkgJT4lCiAga2FibGVfY3VzdG9tKAogICAgY2FwdGlvbnMgPSBjKAogICAgICAiQ29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdGVkIGJlaGF2aW9yIGZyb20gZGVsdGEtcnVsZSBTUnMiLAogICAgICAiKDEwMCB2cyA1MDAwIG9ic2VydmF0aW9ucykiCiAgICApCiAgKQpgYGAKCldoZW4gd2UgcGxvdCBvdXQgdGhlIG1vZGVsIHByZWRpY3Rpb25zLCB3ZSBzZWUgdGhhdCB0aGUgdHdvIGRlbHRhLXJ1bGUgU1JzIGFyZSBtYWtpbmcgZXNzZW50aWFsbHkgdGhlIHNhbWUgcHJlZGljdGlvbnMuCgpgYGB7ciBwbG90LWRlbHRhLXJ1bGUtc3ItbmF2fQojfCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04CgpwbG90X3NyX2RlbHRhX25hdiA8LSBsZWZ0X2pvaW4oCiAgcHJlZGljdGVkX3NyX2RlbHRhXzEwMCAlPiUKICAgIHNlbGVjdCgKICAgICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsCiAgICAgIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLAogICAgICBvcHQxX2lkLCBvcHQyX2lkLAogICAgICBwX2NvcnJlY3RfMTAwID0gcF9jb3JyZWN0CiAgICApLAogIHByZWRpY3RlZF9zcl9kZWx0YV81MDAwICU+JQogICAgc2VsZWN0KAogICAgICBzcl9nYW1tYSwgc2hvcnRlc3RfcGF0aCwKICAgICAgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsCiAgICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICAgIHBfY29ycmVjdF81MDAwID0gcF9jb3JyZWN0CiAgICApLAogIGJ5ID0gam9pbl9ieSgKICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICkKKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoInBfY29ycmVjdF8iKSwKICAgIG5hbWVzX3RvID0gIm5fb2JzIiwKICAgIHZhbHVlc190byA9ICJwX2NvcnJlY3QiCiAgKSAlPiUKICBtdXRhdGUoCiAgICBuX29icyA9IHN0cl9yZW1vdmUobl9vYnMsICJwX2NvcnJlY3RfIiksCiAgICBzaG9ydGVzdF9wYXRoID0gc3RyX2MoIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UgIiwgc2hvcnRlc3RfcGF0aCksCiAgICBzcl9nYW1tYSA9IHN0cl9jKHVuaWNvZGVfZ3JlZWtbImdhbW1hIl0sICI9Iiwgc3JfZ2FtbWEpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bl9vYnMsIHk9cF9jb3JyZWN0KSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF9ncmlkKAogICAgcm93cyA9IHZhcnMoc3JfZ2FtbWEpLAogICAgY29scyA9IHZhcnMoc2hvcnRlc3RfcGF0aCkKICApICsKICBnZW9tX2xpbmUoCiAgICBhZXMoZ3JvdXAgPSBpbnRlcmFjdGlvbihzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZCkpLAogICAgYWxwaGEgPSAwLjEKICApICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiIyBPYnNlcnZhdGlvbnMiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgbmFtZSA9ICJBY2N1cmFjeSIsCiAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsCiAgICBicmVha3MgPSBzZXEoMC41LCAxLCAwLjI1KQogICkgKwogIGdndGl0bGUoIlByZWRpY3RlZCBuYXZpZ2F0aW9uIGFjY3VyYWN5OiBEZWx0YS1ydWxlIFNSIikKCnBsb3Rfc3JfZGVsdGFfbmF2CgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfZGVsdGFfcnVsZV8xMDBfdnNfNTAwMC5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3NyX2RlbHRhX25hdiwKICAgIHdpZHRoID0gNiwgaGVpZ2h0ID0gOCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwLAogICAgZGV2aWNlID0gY2Fpcm9fcGRmCiAgKQp9CmBgYAoKIyMgQW5hbHl0aWMgdnMgZGVsdGEtcnVsZSBTUnMKCkZpbmFsbHksIGxldCdzIGNvbXBhcmUgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIGFuYWx5dGljIGFuZCBkZWx0YS1ydWxlIFNScy4KCldlIGNhbiBzZWUgdGhhdCB0aGUgcHJlZGljdGVkIGJlaGF2aW9yIGZyb20gdGhlIGRlbHRhLXJ1bGUgU1IgKGxlYXJuZWQgZnJvbSAxMDAgb2JzZXJ2YXRpb25zKSBjb3JyZWxhdGVzIHZlcnkgc3Ryb25nbHkgd2l0aCBwcmVkaWN0ZWQgYmVoYXZpb3IgZnJvbSB0aGUgYXN5bXB0b3RpYyBTUi4KCmBgYHtyIGNvcnItYW5hbHl0aWMtZGVsdGEtc3J9CmxlZnRfam9pbigKICBwcmVkaWN0ZWRfc3JfZGVsdGFfMTAwICU+JQogICAgc2VsZWN0KHNyX2dhbW1hOm9wdDJfaWQsIHNyXzEwMCA9IHBfY29ycmVjdCksCiAgcHJlZGljdGVkX3NyX2FuYWx5dGljX2NvdW50cyAlPiUKICAgIHNlbGVjdChzcl9nYW1tYTpvcHQyX2lkLCBzcl9hbmFseXRpYyA9IHBfY29ycmVjdCksCiAgYnkgPSBqb2luX2J5KAogICAgc3JfZ2FtbWEsIHNob3J0ZXN0X3BhdGgsIHN0YXJ0cG9pbnRfaWQsIGVuZHBvaW50X2lkLCBvcHQxX2lkLCBvcHQyX2lkCiAgKQopICU+JQogIGdyb3VwX2J5KHNyX2dhbW1hKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKAogICAgY29yciA9IG1hcF9kYmwoLnggPSBkYXRhLCAuZiA9IH53aXRoKC54LCBjb3Ioc3JfMTAwLCBzcl9hbmFseXRpYykpKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdCgtZGF0YSkgJT4lCiAga2FibGVfY3VzdG9tKAogICAgY2FwdGlvbnMgPSBjKAogICAgICAiQ29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdGVkIGJlaGF2aW9yIiwKICAgICAgIihkZWx0YS1ydWxlIHZzIGFuYWx5dGljIFNScykiCiAgICApCiAgKQpgYGAKCkhvd2V2ZXIsIHdoZW4gd2UgcGxvdCBvdXQgdGhlIG1vZGVsIHByZWRpY3Rpb25zLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIHZlcnkgc3Ryb25nIGRpdmVyZ2VuY2VzIGluIHdoYXQgYmVoYXZpb3JzIHRoZSBhbmFseXRpYyB2cyBkZWx0YS1ydWxlIG1ldGhvZHMgcHJlZGljdC4KCmBgYHtyIHBsb3QtYW5hbHl0aWMtZGVsdGEtc3ItMX0KI3wgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9OAoKcGxvdF9zcl9jb21wYXJpc29uXzEgPC0gbGVmdF9qb2luKAogIHByZWRpY3RlZF9zcl9hbmFseXRpY19jb3VudHMgJT4lCiAgICBzZWxlY3QoCiAgICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLAogICAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgcF9jb3JyZWN0X2FuYWx5dGljID0gcF9jb3JyZWN0CiAgICApLAogIHByZWRpY3RlZF9zcl9kZWx0YV8xMDAgJT4lCiAgICBzZWxlY3QoCiAgICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLAogICAgICBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwKICAgICAgb3B0MV9pZCwgb3B0Ml9pZCwKICAgICAgcF9jb3JyZWN0X2RlbHRhID0gcF9jb3JyZWN0CiAgICApLAogIGJ5ID0gam9pbl9ieSgKICAgIHNyX2dhbW1hLCBzaG9ydGVzdF9wYXRoLCBzdGFydHBvaW50X2lkLCBlbmRwb2ludF9pZCwgb3B0MV9pZCwgb3B0Ml9pZAogICkKKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoInBfY29ycmVjdF8iKSwKICAgIG5hbWVzX3RvID0gIm1ldGhvZCIsCiAgICB2YWx1ZXNfdG8gPSAicF9jb3JyZWN0IgogICkgJT4lCiAgbXV0YXRlKAogICAgbWV0aG9kID0gc3RyX3JlbW92ZShtZXRob2QsICJwX2NvcnJlY3RfIiksCiAgICBzaG9ydGVzdF9wYXRoID0gc3RyX2MoIlNob3J0ZXN0IHBhdGggZGlzdGFuY2UgIiwgc2hvcnRlc3RfcGF0aCksCiAgICBzcl9nYW1tYSA9IHN0cl9jKHVuaWNvZGVfZ3JlZWtbImdhbW1hIl0sICI9Iiwgc3JfZ2FtbWEpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWV0aG9kLCB5PXBfY29ycmVjdCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfZ3JpZCgKICAgIHJvd3MgPSB2YXJzKHNyX2dhbW1hKSwKICAgIGNvbHMgPSB2YXJzKHNob3J0ZXN0X3BhdGgpCiAgKSArCiAgZ2VvbV9saW5lKAogICAgYWVzKGdyb3VwID0gaW50ZXJhY3Rpb24oc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQpKSwKICAgIGFscGhhID0gMC4xCiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIG5hbWUgPSAiTWV0aG9kIiwKICAgIGxhYmVscyA9IGMoImFuYWx5dGljIj0iQW5hbHl0aWMiLCAiZGVsdGEiPSJEZWx0YS1ydWxlIikKICApICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkFjY3VyYWN5IiwKICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGJyZWFrcyA9IHNlcSgwLjUsIDEsIDAuMjUpCiAgKSArCiAgZ2d0aXRsZSgiUHJlZGljdGVkIG5hdmlnYXRpb24gYWNjdXJhY3kiKQoKcGxvdF9zcl9jb21wYXJpc29uXzEKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgKICAgICAgIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfYW5hbHl0aWNfdnNfZGVsdGFfcnVsZV8xLnBkZiIKICAgICksCiAgICBwbG90ID0gcGxvdF9zcl9jb21wYXJpc29uXzEsCiAgICB3aWR0aCA9IDYsIGhlaWdodCA9IDgsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMCwKICAgIGRldmljZSA9IGNhaXJvX3BkZgogICkKfQpgYGAKCldlIGNhbiBlbXBoYXNpemUgdGhpcyBieSBwbG90dGluZyBpdCBpbiBhIGRpZmZlcmVudCB3YXkuIFRoaXMgaGlnaGxpZ2h0cyB0aGF0IChhdCBsZWFzdCBhdCB0aGlzIHNvZnRtYXggdGVtcGVyYXR1cmUpIHRoZSBhbmFseXRpYyBhbmQgZGVsdGEtcnVsZSBpbXBsZW1lbnRhdGlvbnMgYmFzaWNhbGx5IGNvbnZlcmdlIGF0IGxhcmdlciB2YWx1ZXMgb2YgZ2FtbWEsIGJ1dCByYWRpY2FsbHkgZGl2ZXJnZSBhdCBsb3dlciB2YWx1ZXMgb2YgZ2FtbWEuCgpgYGB7ciBwbG90LWFuYWx5dGljLWRlbHRhLXNyLTJ9CiN8IGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTUKCnBsb3Rfc3JfY29tcGFyaXNvbl8yIDwtIGxlZnRfam9pbigKICBwcmVkaWN0ZWRfc3JfYW5hbHl0aWNfY291bnRzICU+JQogICAgc2VsZWN0KAogICAgICBzcl9nYW1tYSwgc2hvcnRlc3RfcGF0aCwKICAgICAgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsCiAgICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICAgIHBfY29ycmVjdF9hbmFseXRpYyA9IHBfY29ycmVjdAogICAgKSwKICBwcmVkaWN0ZWRfc3JfZGVsdGFfMTAwICU+JQogICAgc2VsZWN0KAogICAgICBzcl9nYW1tYSwgc2hvcnRlc3RfcGF0aCwKICAgICAgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsCiAgICAgIG9wdDFfaWQsIG9wdDJfaWQsCiAgICAgIHBfY29ycmVjdF9kZWx0YSA9IHBfY29ycmVjdAogICAgKSwKICBieSA9IGpvaW5fYnkoCiAgICBzcl9nYW1tYSwgc2hvcnRlc3RfcGF0aCwgc3RhcnRwb2ludF9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQsIG9wdDJfaWQKICApCikgJT4lCiAgcGl2b3RfbG9uZ2VyKAogICAgY29scyA9IHN0YXJ0c193aXRoKCJwX2NvcnJlY3RfIiksCiAgICBuYW1lc190byA9ICJtZXRob2QiLAogICAgdmFsdWVzX3RvID0gInBfY29ycmVjdCIKICApICU+JQogIG11dGF0ZSgKICAgIG1ldGhvZCA9IHN0cl9yZW1vdmUobWV0aG9kLCAicF9jb3JyZWN0XyIpLAogICAgbWV0aG9kID0gaWZfZWxzZShtZXRob2QgPT0gImFuYWx5dGljIiwgIkFuYWx5dGljIiwgIkRlbHRhLXJ1bGUiKSwKICAgIHNyX2dhbW1hID0gc3RyX2ModW5pY29kZV9ncmVla1siZ2FtbWEiXSwgIj0iLCBzcl9nYW1tYSkKICApICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfY29ycmVjdCwgY29sb3I9bWV0aG9kKSkgKwogIHRoZW1lX2N1c3RvbSgpICsKICBmYWNldF93cmFwKH5zcl9nYW1tYSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHN0YXRfc3VtbWFyeSgKICAgIGFlcyhncm91cCA9IG1ldGhvZCksIGdlb20gPSAibGluZSIsIGZ1biA9IG1lYW4sIGxpbmV3aWR0aCA9IDEKICApICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIG5hbWUgPSAiU1IgaW1wbGVtZW50YXRpb24iLAogICAgdmFsdWVzID0gYygiQW5hbHl0aWMiPSIjY2EwMDIwIiwgIkRlbHRhLXJ1bGUiPSIjMDU3MWIwIikKICApICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkFjY3VyYWN5IiwKICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwKICAgIGJyZWFrcyA9IHNlcSgwLjUsIDEsIDAuMjUpCiAgKSArCiAgZ2d0aXRsZSgiUHJlZGljdGVkIG5hdmlnYXRpb24gYWNjdXJhY3kiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpwbG90X3NyX2NvbXBhcmlzb25fMgoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKAogICAgICAib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJzcl9hbmFseXRpY192c19kZWx0YV9ydWxlXzIucGRmIgogICAgKSwKICAgIHBsb3QgPSBwbG90X3NyX2NvbXBhcmlzb25fMiwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwLAogICAgZGV2aWNlID0gY2Fpcm9fcGRmCiAgKQp9CmBgYAoKIyMgTW9kZWwgcHJlZGljdGlvbnMKClRha2luZyBzdG9jayBvZiB3aGF0IHdlJ3ZlIGRlbW9uc3RyYXRlZCBzbyBmYXI6CgoxLiBJdCBkb2Vzbid0IG1hdHRlciB3aGV0aGVyIFNScyBhcmUgaW1wbGVtZW50ZWQgYXMgZW5jb2RpbmcgY291bnRzIG9yIHByb2JhYmlsaXRpZXMuIFRoZXkgY29udGFpbiB0aGUgc2FtZSBpbmZvcm1hdGlvbiwgYW5kIHRoZXkgbWFrZSBpZGVudGljYWwgYmVoYXZpb3JhbCBwcmVkaWN0aW9ucyB3aGVuIGFkanVzdGluZyB0aGUgc29mdG1heCB0ZW1wZXJhdHVyZSBwYXJhbWV0ZXIgYXBwcm9wcmlhdGVseS4KCjIuIFRoZSBkZWx0YS1ydWxlIHZlcnNpb24gb2YgdGhlIFNSIGlzIHNlbnNpdGl2ZSB0byB3aGF0IG9ic2VydmF0aW9ucyBpdCBsZWFybnMgZnJvbS4gVGhhdCBzYWlkLCB3ZSBjYW4gYWNoaWV2ZSBzb21ldGhpbmcgdGhhdCBsb29rcyBsaWtlIGFuICJhc3ltcHRvdGljIiBTUiB3aXRoIGFzIGZldyBhcyAxMDAgb2JzZXJ2YXRpb25zLgoKMy4gVGhlIGFuYWx5dGljIGFuZCBkZWx0YS1ydWxlIFNScyBtYWtlIGhpZ2hseSBjb3JyZWxhdGVkIHByZWRpY3Rpb25zLiBIb3dldmVyLCBpbiB0ZXJtcyBvZiBhYnNvbHV0ZSBjaG9pY2UgYWNjdXJhY3ksIHRoZXkgbWFrZSB2ZXJ5IGRpZmZlcmVudCBwcmVkaWN0aW9ucyBhdCBzb21lIHZhbHVlcyBvZiBnYW1tYS4KCldlIGhhdmVuJ3QgeWV0IGVzdGFibGlzaGVkIGhvdyB0aGUgc29mdG1heCAoaW52ZXJzZSkgdGVtcGVyYXR1cmUgcGFyYW1ldGVyIGFmZmVjdHMgcHJlZGljdGVkIGNob2ljZSwgZXNwZWNpYWxseSBpbiBjb21iaW5hdGlvbiB3aXRoIHRoZSBnYW1tYSBwYXJhbWV0ZXIsIHNvIHdlIG5lZWQgdG8gYnVpbGQgYW4gaW50dWl0aW9uIGZvciB0aGlzLiBUbyBkbyBzbywgd2UnbGwgY2hlYXQgYSBsaXR0bGUgYml0OiBpZiB5b3UncmUgZm9sbG93aW5nIHRoZXNlIHNjcmlwdHMgaW4gbGluZWFyIG9yZGVyLCB3ZSBkb24ndCB5ZXQga25vdyB0aGF0LCBlbXBpcmljYWxseSwgdGhlIGdvb2RuZXNzLW9mLWZpdCBpcyBlc3NlbnRpYWxseSBpZGVudGljYWwgYmV0d2VlbiB0aGUgYW5hbHl0aWMgYW5kIGRlbHRhLXJ1bGUgdmFyaWFudHMgb2YgdGhlIFNSLiBIb3dldmVyLCBJJ20gd3JpdGluZyB0aGlzIG5vdGVib29rIGFmdGVyIGhhdmluZyBkb25lIHRob3NlIGFuYWx5c2VzLCBzbyBJJ20ganVzdCB0ZWxsaW5nIHlvdSB0aGF0IHRoaXMgaXMgdHJ1ZS4gU28sIGZvciB0aGUgcHVycG9zZSBvZiBkb2luZyBwYXJhbWV0ZXIgcmVjb3Zlcnkgc2ltdWxhdGlvbnMsIHdlJ2xsIGp1c3QgdXNlIHRoZSBhbmFseXRpYyBtZXRob2QgdG8gZ2VuZXJhdGUgbW9kZWwgcHJlZGljdGlvbnMgLyBsYXRlciBmaXQgcGFyYW1ldGVycyBvbiBzaW11bGF0ZWQgZGF0YS4KCmBgYHtyIHNyLXByZWRpY3RlZH0KcHJlZGljdGVkX3NyX3JlcHJlc2VudGF0aW9uIDwtIG1hcF9kZnIoCiAgLnggPSBjKHNlcSgwLjEsIDAuOSwgMC4xKSwgMC45OSksCiAgLmYgPSB+YnVpbGRfc3VjY2Vzc29yX2FuYWx5dGljYWxseSgKICAgIHRyYW5zbWF0LCBzdWNjZXNzb3JfaG9yaXpvbiA9IC54LCBub3JtYWxpemUgPSBUUlVFCiAgKQopCgpwcmVkaWN0ZWRfc3JfbmF2aWdhdGlvbiA8LSBleHBhbmRfZ3JpZCgKICBzcl9nYW1tYSA9IGMoc2VxKDAuMSwgMC45LCAwLjEpLCAwLjk5KSwKICBzb2Z0bWF4X3RlbXBlcmF0dXJlID0gc2VxKDEwMCwgOTAwLCAxMDApLAogIG5hdl90cmlhbHMKKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBwcmVkaWN0ZWRfc3JfcmVwcmVzZW50YXRpb24gJT4lCiAgICAgIHJlbmFtZShlbmRwb2ludF9pZCA9IHRvLCBvcHQxX2lkID0gZnJvbSwgb3B0MV9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzcl9nYW1tYSwgZW5kcG9pbnRfaWQsIG9wdDFfaWQpCiAgKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBwcmVkaWN0ZWRfc3JfcmVwcmVzZW50YXRpb24gJT4lCiAgICAgIHJlbmFtZShlbmRwb2ludF9pZCA9IHRvLCBvcHQyX2lkID0gZnJvbSwgb3B0Ml9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzcl9nYW1tYSwgZW5kcG9pbnRfaWQsIG9wdDJfaWQpCiAgKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jb3JyZWN0ID0gc29mdG1heCgKICAgICAgb3B0aW9uX3ZhbHVlcyA9IGMob3B0MV9zciwgb3B0Ml9zciksCiAgICAgIG9wdGlvbl9jaG9zZW4gPSBpZl9lbHNlKG9wdDFfaWQgPT0gY29ycmVjdF9jaG9pY2UsIDEsIDIpLAogICAgICB0ZW1wZXJhdHVyZSA9IHNvZnRtYXhfdGVtcGVyYXR1cmUsCiAgICAgIHVzZV9pbnZlcnNlX3RlbXBlcmF0dXJlID0gVFJVRQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpCmBgYAoKVGhlIHBsb3RzIGJlbG93IG1ha2UgY2xlYXIgdHdvIHRoaW5nczogMSkgYXMgYSBnZW5lcmFsIHJ1bGUsIGhpZ2hlci1nYW1tYSBTUnMgc2F0dXJhdGUgYXQgc21hbGxlciB2YWx1ZXMgb2YgdGhlIGludmVyc2UgdGVtcGVyYXR1cmUgcGFyYW1ldGVyLCBhbmQgbWF5IGluIGZhY3QgYmUgaGFyZCB0byBpZGVudGlmeSBpZiB0aGUgc29mdG1heCB0ZW1wZXJhdHVyZSBpcyBzdWZmaWNpZW50bHkgaGlnaCwgYW5kIDIpIGZvciBsb3dlci1nYW1tYSBTUnMsIHNhdHVyYXRpb24gZXZpZGVudGx5IHJlcXVpcmVzIGZhaXJseSBsYXJnZSB2YWx1ZXMgb2YgdGhlIGludmVyc2UgdGVtcGVyYXR1cmUgcGFyYW1ldGVyLiBUaGlzIG1ha2VzIGl0IGEgbGl0dGxlIGhhcmQgdG8gY2hvb3NlIGdvb2QgdmFsdWVzIG9mIHRoZSB0ZW1wZXJhdHVyZSBwYXJhbWV0ZXIgdG8gc2ltdWxhdGUgb3Zlci4KCmBgYHtyIHBsb3Qtc3J9CiN8IGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMAoKcGxvdF9zcl90cmlhbHMgPC0gcHJlZGljdGVkX3NyX25hdmlnYXRpb24gJT4lCiAgbXV0YXRlKAogICAgc29mdG1heF90ZW1wZXJhdHVyZSA9IHN0cl9jKAogICAgICAiSW52LiB0ZW1wLj0iLAogICAgICBzdHJfcGFkKHNvZnRtYXhfdGVtcGVyYXR1cmUsIHdpZHRoID0gMywgc2lkZSA9ICJsZWZ0IiwgcGFkID0gIjAiKQogICAgKSwKICAgIHNyX2dhbW1hID0gc3RyX2ModW5pY29kZV9ncmVla1siZ2FtbWEiXSwgIj0iLCBzcl9nYW1tYSkKICApICU+JQogIGdncGxvdChhZXMoeD1zaG9ydGVzdF9wYXRoLCB5PXBfY29ycmVjdCkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZmFjZXRfZ3JpZCgKICAgIHJvd3MgPSB2YXJzKHNyX2dhbW1hKSwKICAgIGNvbHMgPSB2YXJzKHNvZnRtYXhfdGVtcGVyYXR1cmUpCiAgKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKwogIHN0YXRfc3VtbWFyeShhZXMoZ3JvdXAgPSAxKSwgZ2VvbSA9ICJsaW5lIiwgZnVuID0gbWVhbiwgbGluZXdpZHRoID0gMSkgKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJTaG9ydGVzdCBwYXRoIGRpc3RhbmNlIikgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIG5hbWUgPSAiQWNjdXJhY3kiLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMC41LCAxKQogICkgKwogIGdndGl0bGUoIlNpbXVsYXRlZCBTdWNjZXNzb3IgUmVwLiIpCgpwbG90X3NyX3N1bW1hcnkgPC0gcHJlZGljdGVkX3NyX25hdmlnYXRpb24gJT4lCiAgZ3JvdXBfYnkoc3JfZ2FtbWEsIHNvZnRtYXhfdGVtcGVyYXR1cmUsIHNob3J0ZXN0X3BhdGgpICU+JQogIHN1bW1hcmlzZShwX2NvcnJlY3QgPSBtZWFuKHBfY29ycmVjdCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIG11dGF0ZShzcl9nYW1tYSA9IHN0cl9jKHVuaWNvZGVfZ3JlZWtbImdhbW1hIl0sICI9Iiwgc3JfZ2FtbWEpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9c2hvcnRlc3RfcGF0aCwgeT1wX2NvcnJlY3QsIGNvbG9yPXNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgZmFjZXRfd3JhcCh+c3JfZ2FtbWEsIG5jb2wgPSA1KSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBzb2Z0bWF4X3RlbXBlcmF0dXJlKSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygKICAgIG5hbWUgPSAiSW52LiB0ZW1wLiIsIG9wdGlvbiA9ICJ0dXJibyIsIGVuZCA9IDAuOSwgZGlyZWN0aW9uID0gLTEKICApICsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiU2hvcnRlc3QgcGF0aCBkaXN0YW5jZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoCiAgICBuYW1lID0gIkFjY3VyYWN5IiwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAuNSwgMSkKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKwogIGdndGl0bGUoIlNpbXVsYXRlZCBTdWNjZXNzb3IgUmVwLiIpCgpwbG90X3NyX3RyaWFscwpwbG90X3NyX3N1bW1hcnkKCmlmIChrbml0dGluZykgewogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJzcl90cmlhbHMucGRmIiksCiAgICBwbG90ID0gcGxvdF9zcl90cmlhbHMsCiAgICB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwLAogICAgZGV2aWNlID0gY2Fpcm9fcGRmCiAgKQogIAogIGdnc2F2ZSgKICAgIGZpbGVuYW1lID0gaGVyZSgib3V0cHV0cyIsIHdvcmtmbG93X25hbWUsICJzcl9zdW1tYXJ5LnBkZiIpLAogICAgcGxvdCA9IHBsb3Rfc3Jfc3VtbWFyeSwKICAgIHdpZHRoID0gNSwgaGVpZ2h0ID0gNSwKICAgIHVuaXRzID0gImluIiwgZHBpID0gMzAwLAogICAgZGV2aWNlID0gY2Fpcm9fcGRmCiAgKQp9CmBgYAoKIyMgU2ltdWxhdGUgYmVoYXZpb3IKCkluIHRoaXMgY2FzZSwgYSBnb29kIHN0cmF0ZWd5IG1pZ2h0IGJlIHRvIGFzc3VtZSB0aGF0IHRoZSBzb2Z0bWF4IHRlbXBlcmF0dXJlcyBjb21lIGZyb20gYSBoYWxmLUdhdXNzaWFuIGRpc3RyaWJ1dGlvbiB3aXRoIGEgZmFpcmx5IGxpYmVyYWwgU0QuIE5vdGUgdGhhdCwgYXMgd2FzIHRoZSBjYXNlIHdpdGggQkZTLWZvcndhcmQsIGEgcHJldmlvdXMgdmVyc2lvbiBvZiB0aGlzIHdvcmsgaGFkIGVzdGltYXRlZCBhIGxhcHNlIHJhdGUuIFRvIGRlbW9uc3RyYXRlIHRoYXQgdGhlIGxhcHNlIHJhdGUgaGFzIHBvb3IgcmVjb3ZlcmFiaWxpdHksIHdlJ2xsIHNpbXVsYXRlIHR3byBzZXRzIG9mIGJlaGF2aW9ycywgb25lIGFmZmVjdGVkIGJ5IGEgbGFwc2UgcmF0ZSwgb25lIG5vdC4KCmBgYHtyIHNpbS1zcn0Kc2V0LnNlZWQoc3VtKHV0ZjhUb0ludCgiVGhhbmsgeW91LCBuZXh0IikpKQoKc2ltX3BhcmFtc19zciA8LSB0aWJibGUoCiAgc3ViX2lkID0gMTo1MDAsCiAgc3JfZ2FtbWEgPSBydW5pZihuID0gNTAwLCBtaW4gPSAwLCBtYXggPSAwLjk5KSwKICBzb2Z0bWF4X3RlbXBlcmF0dXJlID0gcm5vcm0obiA9IDUwMCwgbWVhbiA9IDAsIHNkID0gNDAwKSwKICBsYXBzZV9yYXRlID0gcnVuaWYobiA9IDUwMCwgbWluID0gMCwgbWF4ID0gMSkKKSAlPiUKICBtdXRhdGUoc29mdG1heF90ZW1wZXJhdHVyZSA9IGFicyhzb2Z0bWF4X3RlbXBlcmF0dXJlKSkKCnNpbV9yZXBfc3IgPC0gc2ltX3BhcmFtc19zciAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcHJlZGljdGVkX3NyID0gbWFwKAogICAgICAueCA9IHNyX2dhbW1hLAogICAgICAuZiA9IH5idWlsZF9zdWNjZXNzb3JfYW5hbHl0aWNhbGx5KAogICAgICAgIHRyYW5zbWF0LCBzdWNjZXNzb3JfaG9yaXpvbiA9IC54LCBub3JtYWxpemUgPSBUUlVFCiAgICAgICkKICAgICkKICApICU+JQogIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3Qoc3ViX2lkLCBwcmVkaWN0ZWRfc3IpICU+JQogIHVubmVzdChwcmVkaWN0ZWRfc3IpCgpzaW1fYmVoYXZfc3IgPC0gZXhwYW5kX2dyaWQoCiAgc3ViX2lkID0gMTo1MDAsCiAgbmF2X3RyaWFscwopICU+JQogICMgQWRkIHN1YmplY3Qtc3BlY2lmaWMgcGFyYW1ldGVycwogIGxlZnRfam9pbihzaW1fcGFyYW1zX3NyLCBieSA9IGpvaW5fYnkoc3ViX2lkKSkgJT4lCiAgc2VsZWN0KC1sYXBzZV9yYXRlKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHByZWRpY3RlZCBTUgogIGxlZnRfam9pbigKICAgIHNpbV9yZXBfc3IgJT4lCiAgICAgIHNlbGVjdChzdWJfaWQsIGVuZHBvaW50X2lkID0gdG8sIG9wdDFfaWQgPSBmcm9tLCBvcHQxX3NyID0gc3JfdmFsdWUpLAogICAgYnkgPSBqb2luX2J5KHN1Yl9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQpCiAgKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBzaW1fcmVwX3NyICU+JQogICAgICBzZWxlY3Qoc3ViX2lkLCBlbmRwb2ludF9pZCA9IHRvLCBvcHQyX2lkID0gZnJvbSwgb3B0Ml9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzdWJfaWQsIGVuZHBvaW50X2lkLCBvcHQyX2lkKQogICkgJT4lCiAgIyBDYWxjdWxhdGUgcHJvYmFiaWxpdHkgb2YgY2hvb3Npbmcgb3B0MSBnaXZlbiBwYXJhbWV0ZXJzICsgcHJlZGljdGVkIFNSCiAgIyBUaGVuIG1ha2UgYmluYXJ5IGNob2ljZSBpbiBwcm9wb3J0aW9uIHRvIHRoYXQgcHJvYmFiaWxpdHkKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jaG9vc2Vfb3B0MSA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKG9wdDFfc3IsIG9wdDJfc3IpLAogICAgICBvcHRpb25fY2hvc2VuID0gMSwKICAgICAgdGVtcGVyYXR1cmUgPSBzb2Z0bWF4X3RlbXBlcmF0dXJlLAogICAgICB1c2VfaW52ZXJzZV90ZW1wZXJhdHVyZSA9IFRSVUUKICAgICksCiAgICBzaW11bGF0ZWRfY2hvaWNlID0gc2FtcGxlKAogICAgICBjKG9wdDFfaWQsIG9wdDJfaWQpLCBzaXplID0gMSwgcHJvYiA9IGMocF9jaG9vc2Vfb3B0MSwgMS1wX2Nob29zZV9vcHQxKQogICAgKQogICkgJT4lCiAgdW5ncm91cCgpCgpzaW1fYmVoYXZfc3Jfd2l0aF9sYXBzZSA8LSBleHBhbmRfZ3JpZCgKICBzdWJfaWQgPSAxOjUwMCwKICBuYXZfdHJpYWxzCikgJT4lCiAgIyBBZGQgc3ViamVjdC1zcGVjaWZpYyBwYXJhbWV0ZXJzCiAgbGVmdF9qb2luKHNpbV9wYXJhbXNfc3IsIGJ5ID0gam9pbl9ieShzdWJfaWQpKSAlPiUKICAjIEFkZCBzdWJqZWN0LXNwZWNpZmljIHByZWRpY3RlZCBTUgogIGxlZnRfam9pbigKICAgIHNpbV9yZXBfc3IgJT4lCiAgICAgIHNlbGVjdChzdWJfaWQsIGVuZHBvaW50X2lkID0gdG8sIG9wdDFfaWQgPSBmcm9tLCBvcHQxX3NyID0gc3JfdmFsdWUpLAogICAgYnkgPSBqb2luX2J5KHN1Yl9pZCwgZW5kcG9pbnRfaWQsIG9wdDFfaWQpCiAgKSAlPiUKICBsZWZ0X2pvaW4oCiAgICBzaW1fcmVwX3NyICU+JQogICAgICBzZWxlY3Qoc3ViX2lkLCBlbmRwb2ludF9pZCA9IHRvLCBvcHQyX2lkID0gZnJvbSwgb3B0Ml9zciA9IHNyX3ZhbHVlKSwKICAgIGJ5ID0gam9pbl9ieShzdWJfaWQsIGVuZHBvaW50X2lkLCBvcHQyX2lkKQogICkgJT4lCiAgIyBDYWxjdWxhdGUgcHJvYmFiaWxpdHkgb2YgY2hvb3Npbmcgb3B0MSBnaXZlbiBwYXJhbWV0ZXJzICsgcHJlZGljdGVkIFNSCiAgIyBUaGVuIG1ha2UgYmluYXJ5IGNob2ljZSBpbiBwcm9wb3J0aW9uIHRvIHRoYXQgcHJvYmFiaWxpdHkKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgcF9jaG9vc2Vfb3B0MSA9IHNvZnRtYXgoCiAgICAgIG9wdGlvbl92YWx1ZXMgPSBjKG9wdDFfc3IsIG9wdDJfc3IpLAogICAgICBvcHRpb25fY2hvc2VuID0gMSwKICAgICAgdGVtcGVyYXR1cmUgPSBzb2Z0bWF4X3RlbXBlcmF0dXJlLAogICAgICB1c2VfaW52ZXJzZV90ZW1wZXJhdHVyZSA9IFRSVUUsCiAgICAgIGxhcHNlX3JhdGUgPSBsYXBzZV9yYXRlCiAgICApLAogICAgc2ltdWxhdGVkX2Nob2ljZSA9IHNhbXBsZSgKICAgICAgYyhvcHQxX2lkLCBvcHQyX2lkKSwgc2l6ZSA9IDEsIHByb2IgPSBjKHBfY2hvb3NlX29wdDEsIDEtcF9jaG9vc2Vfb3B0MSkKICAgICkKICApICU+JQogIHVuZ3JvdXAoKQoKcGxvdF9wYXJhbV9kaXN0X3NyIDwtIHNpbV9wYXJhbXNfc3IgJT4lCiAgZ2dwbG90KGFlcyh4PXNyX2dhbW1hLCB5PXNvZnRtYXhfdGVtcGVyYXR1cmUpKSArCiAgdGhlbWVfY3VzdG9tKCkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjI1KSArCiAgeGxhYigiR2FtbWEiKSArCiAgeWxhYigiSW52ZXJzZSB0ZW1wZXJhdHVyZSIpICsKICBnZ3RpdGxlKCJTUjogU2ltdWxhdGVkIHBhcmFtZXRlciBkaXN0cmlidXRpb24iKQoKcGxvdF9wYXJhbV9kaXN0X3NyX3NvZnRtYXggPC0gc2ltX3BhcmFtc19zciAlPiUKICBnZ3Bsb3QoYWVzKHg9c29mdG1heF90ZW1wZXJhdHVyZSkpICsKICB0aGVtZV9jdXN0b20oKSArCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgeGxhYigiSW52ZXJzZSB0ZW1wZXJhdHVyZSIpICsKICBnZ3RpdGxlKCJTUjogU2ltdWxhdGVkIHBhcmFtZXRlciBkaXN0cmlidXRpb24iKQoKcGxvdF9wYXJhbV9kaXN0X3NyCnBsb3RfcGFyYW1fZGlzdF9zcl9zb2Z0bWF4CgppZiAoa25pdHRpbmcpIHsKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfcGFyYW1fZGlzdC5wZGYiKSwKICAgIHBsb3QgPSBwbG90X3BhcmFtX2Rpc3Rfc3IsCiAgICB3aWR0aCA9IDUsIGhlaWdodCA9IDUsCiAgICB1bml0cyA9ICJpbiIsIGRwaSA9IDMwMAogICkKICAKICBnZ3NhdmUoCiAgICBmaWxlbmFtZSA9IGhlcmUoIm91dHB1dHMiLCB3b3JrZmxvd19uYW1lLCAic3JfcGFyYW1fZGlzdF9zb2Z0bWF4LnBkZiIpLAogICAgcGxvdCA9IHBsb3RfcGFyYW1fZGlzdF9zcl9zb2Z0bWF4LAogICAgd2lkdGggPSA1LCBoZWlnaHQgPSA1LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCiAgCiAgc2ltX2JlaGF2X3NyICU+JQogICAgd3JpdGVfY3N2KAogICAgICBoZXJlKAogICAgICAgICJkYXRhIiwgInNpbXVsYXRlZF9tb2RlbF9iZWhhdmlvcnMiLAogICAgICAgICJzaW1fbmF2X3NyX25vX2xhcHNlLmNzdiIKICAgICAgKQogICAgKQogIAogIHNpbV9iZWhhdl9zcl93aXRoX2xhcHNlICU+JQogICAgd3JpdGVfY3N2KAogICAgICBoZXJlKAogICAgICAgICJkYXRhIiwgInNpbXVsYXRlZF9tb2RlbF9iZWhhdmlvcnMiLAogICAgICAgICJzaW1fbmF2X3NyX3dpdGhfbGFwc2UuY3N2IgogICAgICApCiAgICApCn0KYGBgCgoKIyBGaWd1cmUgZm9yIHN1cHBsZW1lbnQKClRoZSB2ZXJ5IGxhc3QgdGhpbmcgd2Ugd2FudCB0byBkbyBpcyB0byBjcmVhdGUgYSBmaWd1cmUgb2YgdGhlIHNpbXVsYXRlZCAqYSBwcmlvcmkqIHByZWRpY3Rpb25zIGZvciBhbGwgcGxhbm5pbmcgbW9kZWxzLCB0byBwdXQgaW50byB0aGUgc3VwcGxlbWVudC4KCmBgYHtyIHBsb3QtcGxhbm5pbmctZm9yLXN1cHB9CnBsb3RfbmF2X3ByZWRpY3Rpb25zX2Zvcl9zdXBwIDwtIHdyYXBfcGxvdHMoCiAgcGxvdF9iZnNfYmFja3dhcmRfc3VtbWFyeSArCiAgICBnZ3RpdGxlKCJCRlMtYmFja3dhcmQiKSArCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoCiAgICAgIG5hbWUgPSAiU2VhcmNoIHRocmVzaG9sZCIsIG9wdGlvbiA9ICJ0dXJibyIsIGVuZCA9IDAuOQogICAgKSwKICBwbG90X2Jmc19mb3J3YXJkX3N1bW1hcnkgKwogICAgZ2d0aXRsZSgiQkZTLWZvcndhcmQiKSArCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoCiAgICAgIG5hbWUgPSAiU2VhcmNoIHRocmVzaG9sZCIsIG9wdGlvbiA9ICJ0dXJibyIsIGVuZCA9IDAuOQogICAgKSwKICBwbG90X2lkZWFsX29ic19zdW1tYXJ5ICsKICAgIGdndGl0bGUoIklkZWFsIG9ic2VydmVyIikgKwogICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKAogICAgICBuYW1lID0gIkludmVyc2UgdGVtcGVyYXR1cmUiLAogICAgICBvcHRpb24gPSAiaW5mZXJubyIsIGRpcmVjdGlvbiA9IC0xLCBiZWdpbiA9IDAuNAogICAgKSwKICBucm93ID0gMSwgZ3VpZGVzID0gImNvbGxlY3QiCikgKwogIHBsb3RfYW5ub3RhdGlvbigKICAgIHRpdGxlID0gIlNpbXVsYXRlZCBiZWhhdmlvcjogTW9kZWwtYmFzZWQgcGxhbm5pbmciLAogICAgdGhlbWUgPSB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSksCiAgICB0YWdfbGV2ZWxzID0gIkEiLCB0YWdfc3VmZml4ID0gIi4iCiAgKSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpwbG90X25hdl9wcmVkaWN0aW9uc19mb3Jfc3VwcAoKaWYgKGtuaXR0aW5nKSB7CiAgZ2dzYXZlKAogICAgZmlsZW5hbWUgPSBoZXJlKCJmaWd1cmVzIiwgInN1cHBfc2ltX21vZGVsX2Jhc2VkX3BsYW5uaW5nLnBkZiIpLAogICAgcGxvdCA9IHBsb3RfbmF2X3ByZWRpY3Rpb25zX2Zvcl9zdXBwLAogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA0LAogICAgdW5pdHMgPSAiaW4iLCBkcGkgPSAzMDAKICApCn0KYGBgCgo=